• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 use clap::{value_t, App, Arg};
2 
3 use std::collections::{HashMap, HashSet};
4 use std::sync::{Arc, Mutex};
5 
6 use dbus::channel::MatchingReceiver;
7 use dbus::message::MatchRule;
8 use dbus::nonblock::SyncConnection;
9 use dbus_crossroads::Crossroads;
10 use tokio::sync::mpsc;
11 
12 use crate::bt_adv::AdvSet;
13 use crate::bt_gatt::GattClientContext;
14 use crate::callbacks::{
15     AdminCallback, AdvertisingSetCallback, BtCallback, BtConnectionCallback, BtManagerCallback,
16     BtSocketManagerCallback, ScannerCallback, SuspendCallback,
17 };
18 use crate::command_handler::{CommandHandler, SocketSchedule};
19 use crate::dbus_iface::{
20     BluetoothAdminDBus, BluetoothDBus, BluetoothGattDBus, BluetoothManagerDBus, BluetoothQADBus,
21     BluetoothQALegacyDBus, BluetoothSocketManagerDBus, BluetoothTelephonyDBus, SuspendDBus,
22 };
23 use crate::editor::AsyncEditor;
24 use bt_topshim::topstack;
25 use btstack::bluetooth::{BluetoothDevice, IBluetooth};
26 use btstack::suspend::ISuspend;
27 use manager_service::iface_bluetooth_manager::IBluetoothManager;
28 
29 mod bt_adv;
30 mod bt_gatt;
31 mod callbacks;
32 mod command_handler;
33 mod console;
34 mod dbus_arg;
35 mod dbus_iface;
36 mod editor;
37 
38 /// Context structure for the client. Used to keep track details about the active adapter and its
39 /// state.
40 pub(crate) struct ClientContext {
41     /// List of adapters and whether they are enabled.
42     pub(crate) adapters: HashMap<i32, bool>,
43 
44     // TODO(abps) - Change once we have multi-adapter support.
45     /// The default adapter is also the active adapter. Defaults to 0.
46     pub(crate) default_adapter: i32,
47 
48     /// Current adapter is enabled?
49     pub(crate) enabled: bool,
50 
51     /// Current adapter is ready to be used?
52     pub(crate) adapter_ready: bool,
53 
54     /// Current adapter address if known.
55     pub(crate) adapter_address: Option<String>,
56 
57     /// Currently active bonding attempt. If it is not none, we are currently attempting to bond
58     /// this device.
59     pub(crate) bonding_attempt: Option<BluetoothDevice>,
60 
61     /// Is adapter discovering?
62     pub(crate) discovering_state: bool,
63 
64     /// Devices found in current discovery session. List should be cleared when a new discovery
65     /// session starts so that previous results don't pollute current search.
66     pub(crate) found_devices: HashMap<String, BluetoothDevice>,
67 
68     /// List of bonded devices.
69     pub(crate) bonded_devices: HashMap<String, BluetoothDevice>,
70 
71     /// Proxy for manager interface.
72     pub(crate) manager_dbus: BluetoothManagerDBus,
73 
74     /// Proxy for adapter interface. Only exists when the default adapter is enabled.
75     pub(crate) adapter_dbus: Option<BluetoothDBus>,
76 
77     /// Proxy for adapter QA Legacy interface. Only exists when the default adapter is enabled.
78     pub(crate) qa_legacy_dbus: Option<BluetoothQALegacyDBus>,
79 
80     /// Proxy for adapter QA interface.
81     pub(crate) qa_dbus: Option<BluetoothQADBus>,
82 
83     /// Proxy for GATT interface.
84     pub(crate) gatt_dbus: Option<BluetoothGattDBus>,
85 
86     /// Proxy for Admin interface.
87     pub(crate) admin_dbus: Option<BluetoothAdminDBus>,
88 
89     /// Proxy for suspend interface.
90     pub(crate) suspend_dbus: Option<SuspendDBus>,
91 
92     /// Proxy for socket manager interface.
93     pub(crate) socket_manager_dbus: Option<BluetoothSocketManagerDBus>,
94 
95     /// Proxy for Telephony interface.
96     pub(crate) telephony_dbus: Option<BluetoothTelephonyDBus>,
97 
98     /// Channel to send actions to take in the foreground
99     fg: mpsc::Sender<ForegroundActions>,
100 
101     /// Internal DBus connection object.
102     dbus_connection: Arc<SyncConnection>,
103 
104     /// Internal DBus crossroads object.
105     dbus_crossroads: Arc<Mutex<Crossroads>>,
106 
107     /// Identifies the callback to receive IScannerCallback method calls.
108     scanner_callback_id: Option<u32>,
109 
110     /// Identifies the callback to receive IAdvertisingSetCallback method calls.
111     advertiser_callback_id: Option<u32>,
112 
113     /// Identifies the callback to receive IBluetoothAdminPolicyCallback method calls.
114     admin_callback_id: Option<u32>,
115 
116     /// Keeps track of active LE scanners.
117     active_scanner_ids: HashSet<u8>,
118 
119     /// Keeps track of advertising sets registered. Map from reg_id to AdvSet.
120     adv_sets: HashMap<i32, AdvSet>,
121 
122     /// Identifies the callback to receive IBluetoothSocketManagerCallback method calls.
123     socket_manager_callback_id: Option<u32>,
124 
125     /// Is btclient running in restricted mode?
126     is_restricted: bool,
127 
128     /// Data of GATT client preference.
129     gatt_client_context: GattClientContext,
130 
131     /// The schedule when a socket is connected.
132     socket_test_schedule: Option<SocketSchedule>,
133 
134     /// The handle of the SDP record for MPS (Multi-Profile Specification).
135     mps_sdp_handle: Option<i32>,
136 }
137 
138 impl ClientContext {
new( dbus_connection: Arc<SyncConnection>, dbus_crossroads: Arc<Mutex<Crossroads>>, tx: mpsc::Sender<ForegroundActions>, is_restricted: bool, ) -> ClientContext139     pub fn new(
140         dbus_connection: Arc<SyncConnection>,
141         dbus_crossroads: Arc<Mutex<Crossroads>>,
142         tx: mpsc::Sender<ForegroundActions>,
143         is_restricted: bool,
144     ) -> ClientContext {
145         // Manager interface is almost always available but adapter interface
146         // requires that the specific adapter is enabled.
147         let manager_dbus = BluetoothManagerDBus::new(dbus_connection.clone());
148 
149         ClientContext {
150             adapters: HashMap::new(),
151             default_adapter: 0,
152             enabled: false,
153             adapter_ready: false,
154             adapter_address: None,
155             bonding_attempt: None,
156             discovering_state: false,
157             found_devices: HashMap::new(),
158             bonded_devices: HashMap::new(),
159             manager_dbus,
160             adapter_dbus: None,
161             qa_legacy_dbus: None,
162             qa_dbus: None,
163             gatt_dbus: None,
164             admin_dbus: None,
165             suspend_dbus: None,
166             socket_manager_dbus: None,
167             telephony_dbus: None,
168             fg: tx,
169             dbus_connection,
170             dbus_crossroads,
171             scanner_callback_id: None,
172             advertiser_callback_id: None,
173             admin_callback_id: None,
174             active_scanner_ids: HashSet::new(),
175             adv_sets: HashMap::new(),
176             socket_manager_callback_id: None,
177             is_restricted,
178             gatt_client_context: GattClientContext::new(),
179             socket_test_schedule: None,
180             mps_sdp_handle: None,
181         }
182     }
183 
184     // Sets required values for the adapter when enabling or disabling
set_adapter_enabled(&mut self, hci_interface: i32, enabled: bool)185     fn set_adapter_enabled(&mut self, hci_interface: i32, enabled: bool) {
186         print_info!("hci{} enabled = {}", hci_interface, enabled);
187 
188         self.adapters.entry(hci_interface).and_modify(|v| *v = enabled).or_insert(enabled);
189 
190         // When the default adapter's state is updated, we need to modify a few more things.
191         // Only do this if we're not repeating the previous state.
192         let prev_enabled = self.enabled;
193         let default_adapter = self.default_adapter;
194         if hci_interface == default_adapter && prev_enabled != enabled {
195             self.enabled = enabled;
196             self.adapter_ready = false;
197             if enabled {
198                 self.create_adapter_proxy(hci_interface);
199             } else {
200                 self.adapter_dbus = None;
201             }
202         }
203     }
204 
205     // Creates adapter proxy, registers callbacks and initializes address.
create_adapter_proxy(&mut self, idx: i32)206     fn create_adapter_proxy(&mut self, idx: i32) {
207         let conn = self.dbus_connection.clone();
208 
209         let dbus = BluetoothDBus::new(conn.clone(), idx);
210         self.adapter_dbus = Some(dbus);
211         self.qa_legacy_dbus = Some(BluetoothQALegacyDBus::new(conn.clone(), idx));
212         self.qa_dbus = Some(BluetoothQADBus::new(conn.clone(), idx));
213 
214         let gatt_dbus = BluetoothGattDBus::new(conn.clone(), idx);
215         self.gatt_dbus = Some(gatt_dbus);
216 
217         let admin_dbus = BluetoothAdminDBus::new(conn.clone(), idx);
218         self.admin_dbus = Some(admin_dbus);
219 
220         let socket_manager_dbus = BluetoothSocketManagerDBus::new(conn.clone(), idx);
221         self.socket_manager_dbus = Some(socket_manager_dbus);
222 
223         self.suspend_dbus = Some(SuspendDBus::new(conn.clone(), idx));
224 
225         self.telephony_dbus = Some(BluetoothTelephonyDBus::new(conn.clone(), idx));
226 
227         // Trigger callback registration in the foreground
228         let fg = self.fg.clone();
229         tokio::spawn(async move {
230             let adapter = String::from(format!("adapter{}", idx));
231             let _ = fg.send(ForegroundActions::RegisterAdapterCallback(adapter)).await;
232         });
233     }
234 
235     // Foreground-only: Updates the adapter address.
update_adapter_address(&mut self) -> String236     fn update_adapter_address(&mut self) -> String {
237         let address = self.adapter_dbus.as_ref().unwrap().get_address();
238         self.adapter_address = Some(address.clone());
239 
240         address
241     }
242 
243     // Foreground-only: Updates bonded devices.
update_bonded_devices(&mut self)244     fn update_bonded_devices(&mut self) {
245         let bonded_devices = self.adapter_dbus.as_ref().unwrap().get_bonded_devices();
246 
247         for device in bonded_devices {
248             self.bonded_devices.insert(device.address.clone(), device.clone());
249         }
250     }
251 
connect_all_enabled_profiles(&mut self, device: BluetoothDevice)252     fn connect_all_enabled_profiles(&mut self, device: BluetoothDevice) {
253         let fg = self.fg.clone();
254         tokio::spawn(async move {
255             let _ = fg.send(ForegroundActions::ConnectAllEnabledProfiles(device)).await;
256         });
257     }
258 
run_callback(&mut self, callback: Box<dyn Fn(Arc<Mutex<ClientContext>>) + Send>)259     fn run_callback(&mut self, callback: Box<dyn Fn(Arc<Mutex<ClientContext>>) + Send>) {
260         let fg = self.fg.clone();
261         tokio::spawn(async move {
262             let _ = fg.send(ForegroundActions::RunCallback(callback)).await;
263         });
264     }
265 
get_devices(&self) -> Vec<String>266     fn get_devices(&self) -> Vec<String> {
267         let mut result: Vec<String> = vec![];
268 
269         result.extend(
270             self.found_devices.keys().map(|key| String::from(key)).collect::<Vec<String>>(),
271         );
272         result.extend(
273             self.bonded_devices
274                 .keys()
275                 .filter(|key| !self.found_devices.contains_key(&String::from(*key)))
276                 .map(|key| String::from(key))
277                 .collect::<Vec<String>>(),
278         );
279 
280         result
281     }
282 }
283 
284 /// Actions to take on the foreground loop. This allows us to queue actions in
285 /// callbacks that get run in the foreground context.
286 enum ForegroundActions {
287     ConnectAllEnabledProfiles(BluetoothDevice), // Connect all enabled profiles for this device
288     RunCallback(Box<dyn Fn(Arc<Mutex<ClientContext>>) + Send>), // Run callback in foreground
289     RegisterAdapterCallback(String),            // Register callbacks for this adapter
290     Readline(rustyline::Result<String>),        // Readline result from rustyline
291 }
292 
293 /// Runs a command line program that interacts with a Bluetooth stack.
main() -> Result<(), Box<dyn std::error::Error>>294 fn main() -> Result<(), Box<dyn std::error::Error>> {
295     let matches = App::new("btclient")
296         .arg(Arg::with_name("restricted").long("restricted").takes_value(false))
297         .arg(Arg::with_name("command").short("c").long("command").takes_value(true))
298         .get_matches();
299     let command = value_t!(matches, "command", String);
300     let is_restricted = matches.is_present("restricted");
301 
302     topstack::get_runtime().block_on(async move {
303         // Connect to D-Bus system bus.
304         let (resource, conn) = dbus_tokio::connection::new_system_sync()?;
305 
306         // The `resource` is a task that should be spawned onto a tokio compatible
307         // reactor ASAP. If the resource ever finishes, we lost connection to D-Bus.
308         tokio::spawn(async {
309             let err = resource.await;
310             panic!("Lost connection to D-Bus: {}", err);
311         });
312 
313         // Sets up Crossroads for receiving callbacks.
314         let cr = Arc::new(Mutex::new(Crossroads::new()));
315         cr.lock().unwrap().set_async_support(Some((
316             conn.clone(),
317             Box::new(|x| {
318                 tokio::spawn(x);
319             }),
320         )));
321         let cr_clone = cr.clone();
322         conn.start_receive(
323             MatchRule::new_method_call(),
324             Box::new(move |msg, conn| {
325                 cr_clone.lock().unwrap().handle_message(msg, conn).unwrap();
326                 true
327             }),
328         );
329 
330         // Accept foreground actions with mpsc
331         let (tx, rx) = mpsc::channel::<ForegroundActions>(10);
332 
333         // Create the context needed for handling commands
334         let context = Arc::new(Mutex::new(ClientContext::new(
335             conn.clone(),
336             cr.clone(),
337             tx.clone(),
338             is_restricted,
339         )));
340 
341         // Check if manager interface is valid. We only print some help text before failing on the
342         // first actual access to the interface (so we can also capture the actual reason the
343         // interface isn't valid).
344         if !context.lock().unwrap().manager_dbus.is_valid() {
345             println!("Bluetooth manager doesn't seem to be working correctly.");
346             println!("Check if service is running.");
347             return Ok(());
348         }
349 
350         // TODO: Registering the callback should be done when btmanagerd is ready (detect with
351         // ObjectManager).
352         context.lock().unwrap().manager_dbus.register_callback(Box::new(BtManagerCallback::new(
353             String::from("/org/chromium/bluetooth/client/bluetooth_manager_callback"),
354             context.clone(),
355             conn.clone(),
356             cr.clone(),
357         )));
358 
359         // Check if the default adapter is enabled. If yes, we should create the adapter proxy
360         // right away.
361         let default_adapter = context.lock().unwrap().default_adapter;
362 
363         {
364             let mut context_locked = context.lock().unwrap();
365             match context_locked.manager_dbus.rpc.get_adapter_enabled(default_adapter).await {
366                 Ok(ret) => {
367                     if ret {
368                         context_locked.set_adapter_enabled(default_adapter, true);
369                     }
370                 }
371                 Err(e) => {
372                     panic!("Bluetooth Manager is not available. Exiting. D-Bus error: {}", e);
373                 }
374             }
375         }
376 
377         let mut handler = CommandHandler::new(context.clone());
378 
379         // Allow command line arguments to be read
380         match command {
381             Ok(command) => {
382                 let mut iter = command.split(' ').map(String::from);
383                 handler.process_cmd_line(
384                     &iter.next().unwrap_or(String::from("")),
385                     &iter.collect::<Vec<String>>(),
386                 );
387             }
388             _ => {
389                 start_interactive_shell(handler, tx, rx, context).await?;
390             }
391         };
392         return Result::Ok(());
393     })
394 }
395 
start_interactive_shell( mut handler: CommandHandler, tx: mpsc::Sender<ForegroundActions>, mut rx: mpsc::Receiver<ForegroundActions>, context: Arc<Mutex<ClientContext>>, ) -> Result<(), Box<dyn std::error::Error>>396 async fn start_interactive_shell(
397     mut handler: CommandHandler,
398     tx: mpsc::Sender<ForegroundActions>,
399     mut rx: mpsc::Receiver<ForegroundActions>,
400     context: Arc<Mutex<ClientContext>>,
401 ) -> Result<(), Box<dyn std::error::Error>> {
402     let command_rule_list = handler.get_command_rule_list().clone();
403     let context_for_closure = context.clone();
404 
405     let semaphore_fg = Arc::new(tokio::sync::Semaphore::new(1));
406 
407     // Async task to keep reading new lines from user
408     let semaphore = semaphore_fg.clone();
409     let editor = AsyncEditor::new(command_rule_list, context_for_closure)
410         .map_err(|x| format!("creating async editor failed: {x}"))?;
411     tokio::spawn(async move {
412         loop {
413             // Wait until ForegroundAction::Readline finishes its task.
414             let permit = semaphore.acquire().await;
415             if permit.is_err() {
416                 break;
417             };
418             // Let ForegroundAction::Readline decide when it's done.
419             permit.unwrap().forget();
420 
421             // It's good to do readline now.
422             let result = editor.readline().await;
423             let _ = tx.send(ForegroundActions::Readline(result)).await;
424         }
425     });
426 
427     'readline: loop {
428         let m = rx.recv().await;
429 
430         if m.is_none() {
431             break;
432         }
433 
434         match m.unwrap() {
435             ForegroundActions::ConnectAllEnabledProfiles(device) => {
436                 if context.lock().unwrap().adapter_ready {
437                     context
438                         .lock()
439                         .unwrap()
440                         .adapter_dbus
441                         .as_mut()
442                         .unwrap()
443                         .connect_all_enabled_profiles(device);
444                 } else {
445                     println!("Adapter isn't ready to connect profiles.");
446                 }
447             }
448             ForegroundActions::RunCallback(callback) => {
449                 callback(context.clone());
450             }
451             // Once adapter is ready, register callbacks, get the address and mark it as ready
452             ForegroundActions::RegisterAdapterCallback(adapter) => {
453                 let cb_objpath: String =
454                     format!("/org/chromium/bluetooth/client/{}/bluetooth_callback", adapter);
455                 let conn_cb_objpath: String =
456                     format!("/org/chromium/bluetooth/client/{}/bluetooth_conn_callback", adapter);
457                 let suspend_cb_objpath: String =
458                     format!("/org/chromium/bluetooth/client/{}/suspend_callback", adapter);
459                 let scanner_cb_objpath: String =
460                     format!("/org/chromium/bluetooth/client/{}/scanner_callback", adapter);
461                 let advertiser_cb_objpath: String =
462                     format!("/org/chromium/bluetooth/client/{}/advertising_set_callback", adapter);
463                 let admin_cb_objpath: String =
464                     format!("/org/chromium/bluetooth/client/{}/admin_callback", adapter);
465                 let socket_manager_cb_objpath: String =
466                     format!("/org/chromium/bluetooth/client/{}/socket_manager_callback", adapter);
467 
468                 let dbus_connection = context.lock().unwrap().dbus_connection.clone();
469                 let dbus_crossroads = context.lock().unwrap().dbus_crossroads.clone();
470 
471                 context
472                     .lock()
473                     .unwrap()
474                     .adapter_dbus
475                     .as_mut()
476                     .unwrap()
477                     .rpc
478                     .register_callback(Box::new(BtCallback::new(
479                         cb_objpath.clone(),
480                         context.clone(),
481                         dbus_connection.clone(),
482                         dbus_crossroads.clone(),
483                     )))
484                     .await
485                     .expect("D-Bus error on IBluetooth::RegisterCallback");
486                 context
487                     .lock()
488                     .unwrap()
489                     .adapter_dbus
490                     .as_mut()
491                     .unwrap()
492                     .rpc
493                     .register_connection_callback(Box::new(BtConnectionCallback::new(
494                         conn_cb_objpath,
495                         context.clone(),
496                         dbus_connection.clone(),
497                         dbus_crossroads.clone(),
498                     )))
499                     .await
500                     .expect("D-Bus error on IBluetooth::RegisterConnectionCallback");
501 
502                 // Register callback listener for le-scan`commands.
503                 let scanner_callback_id = context
504                     .lock()
505                     .unwrap()
506                     .gatt_dbus
507                     .as_mut()
508                     .unwrap()
509                     .rpc
510                     .register_scanner_callback(Box::new(ScannerCallback::new(
511                         scanner_cb_objpath.clone(),
512                         context.clone(),
513                         dbus_connection.clone(),
514                         dbus_crossroads.clone(),
515                     )))
516                     .await
517                     .expect("D-Bus error on IBluetoothGatt::RegisterScannerCallback");
518                 context.lock().unwrap().scanner_callback_id = Some(scanner_callback_id);
519 
520                 let advertiser_callback_id = context
521                     .lock()
522                     .unwrap()
523                     .gatt_dbus
524                     .as_mut()
525                     .unwrap()
526                     .rpc
527                     .register_advertiser_callback(Box::new(AdvertisingSetCallback::new(
528                         advertiser_cb_objpath.clone(),
529                         context.clone(),
530                         dbus_connection.clone(),
531                         dbus_crossroads.clone(),
532                     )))
533                     .await
534                     .expect("D-Bus error on IBluetoothGatt::RegisterAdvertiserCallback");
535                 context.lock().unwrap().advertiser_callback_id = Some(advertiser_callback_id);
536 
537                 let admin_callback_id = context
538                     .lock()
539                     .unwrap()
540                     .admin_dbus
541                     .as_mut()
542                     .unwrap()
543                     .rpc
544                     .register_admin_policy_callback(Box::new(AdminCallback::new(
545                         admin_cb_objpath.clone(),
546                         dbus_connection.clone(),
547                         dbus_crossroads.clone(),
548                     )))
549                     .await
550                     .expect("D-Bus error on IBluetoothAdmin::RegisterAdminCallback");
551                 context.lock().unwrap().admin_callback_id = Some(admin_callback_id);
552 
553                 let socket_manager_callback_id = context
554                     .lock()
555                     .unwrap()
556                     .socket_manager_dbus
557                     .as_mut()
558                     .unwrap()
559                     .rpc
560                     .register_callback(Box::new(BtSocketManagerCallback::new(
561                         socket_manager_cb_objpath.clone(),
562                         context.clone(),
563                         dbus_connection.clone(),
564                         dbus_crossroads.clone(),
565                     )))
566                     .await
567                     .expect("D-Bus error on IBluetoothSocketManager::RegisterCallback");
568                 context.lock().unwrap().socket_manager_callback_id =
569                     Some(socket_manager_callback_id);
570 
571                 // When adapter is ready, Suspend API is also ready. Register as an observer.
572                 // TODO(b/224606285): Implement suspend debug utils in btclient.
573                 context.lock().unwrap().suspend_dbus.as_mut().unwrap().register_callback(Box::new(
574                     SuspendCallback::new(
575                         suspend_cb_objpath,
576                         dbus_connection.clone(),
577                         dbus_crossroads.clone(),
578                     ),
579                 ));
580 
581                 context.lock().unwrap().adapter_ready = true;
582                 let adapter_address = context.lock().unwrap().update_adapter_address();
583                 context.lock().unwrap().update_bonded_devices();
584 
585                 print_info!("Adapter {} is ready", adapter_address);
586             }
587             ForegroundActions::Readline(result) => match result {
588                 Err(rustyline::error::ReadlineError::Interrupted) => {
589                     // Ctrl-C cancels the currently typed line, do nothing and ready to do next
590                     // readline again.
591                     semaphore_fg.add_permits(1);
592                 }
593                 Err(_err) => {
594                     break;
595                 }
596                 Ok(line) => {
597                     // Currently Chrome OS uses Rust 1.60 so use the 1-time loop block to
598                     // workaround this.
599                     // With Rust 1.65 onwards we can convert this loop hack into a named block:
600                     // https://blog.rust-lang.org/2022/11/03/Rust-1.65.0.html#break-from-labeled-blocks
601                     // TODO: Use named block when Android and Chrome OS Rust upgrade Rust to 1.65.
602                     loop {
603                         let args = match shell_words::split(line.as_str()) {
604                             Ok(words) => words,
605                             Err(e) => {
606                                 print_error!("Error parsing arguments: {}", e);
607                                 break;
608                             }
609                         };
610 
611                         let (cmd, rest) = match args.split_first() {
612                             Some(pair) => pair,
613                             None => break,
614                         };
615 
616                         if cmd == "quit" {
617                             break 'readline;
618                         }
619 
620                         handler.process_cmd_line(cmd, &rest.to_vec());
621                         break;
622                     }
623 
624                     // Ready to do readline again.
625                     semaphore_fg.add_permits(1);
626                 }
627             },
628         }
629     }
630 
631     semaphore_fg.close();
632 
633     print_info!("Client exiting");
634     Ok(())
635 }
636