• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 use std::collections::HashMap;
2 use std::fmt::{Display, Formatter};
3 use std::slice::SliceIndex;
4 use std::sync::{Arc, Mutex};
5 use std::time::Duration;
6 
7 use crate::bt_adv::AdvSet;
8 use crate::bt_gatt::AuthReq;
9 use crate::callbacks::{BtGattCallback, BtGattServerCallback};
10 use crate::ClientContext;
11 use crate::{console_red, console_yellow, print_error, print_info};
12 use bt_topshim::btif::{BtConnectionState, BtDiscMode, BtStatus, BtTransport};
13 use bt_topshim::profiles::hid_host::BthhReportType;
14 use bt_topshim::profiles::sdp::{BtSdpMpsRecord, BtSdpRecord};
15 use bt_topshim::profiles::{gatt::LePhy, ProfileConnectionState};
16 use btstack::bluetooth::{BluetoothDevice, IBluetooth, IBluetoothQALegacy};
17 use btstack::bluetooth_gatt::{GattWriteType, IBluetoothGatt, ScanSettings, ScanType};
18 use btstack::bluetooth_media::IBluetoothTelephony;
19 use btstack::bluetooth_qa::IBluetoothQA;
20 use btstack::socket_manager::{IBluetoothSocketManager, SocketResult};
21 use btstack::uuid::{Profile, UuidHelper, UuidWrapper};
22 use manager_service::iface_bluetooth_manager::IBluetoothManager;
23 
24 const INDENT_CHAR: &str = " ";
25 const BAR1_CHAR: &str = "=";
26 const BAR2_CHAR: &str = "-";
27 const MAX_MENU_CHAR_WIDTH: usize = 72;
28 const GATT_CLIENT_APP_UUID: &str = "12345678123456781234567812345678";
29 const GATT_SERVER_APP_UUID: &str = "12345678123456781234567812345679";
30 
31 enum CommandError {
32     // Command not handled due to invalid arguments.
33     InvalidArgs,
34     // Command handled but failed with the given reason.
35     Failed(String),
36 }
37 
38 impl From<&str> for CommandError {
from(s: &str) -> CommandError39     fn from(s: &str) -> CommandError {
40         CommandError::Failed(String::from(s))
41     }
42 }
43 
44 impl From<String> for CommandError {
from(s: String) -> CommandError45     fn from(s: String) -> CommandError {
46         CommandError::Failed(s)
47     }
48 }
49 
50 type CommandResult = Result<(), CommandError>;
51 
52 type CommandFunction = fn(&mut CommandHandler, &Vec<String>) -> CommandResult;
53 
_noop(_handler: &mut CommandHandler, _args: &Vec<String>) -> CommandResult54 fn _noop(_handler: &mut CommandHandler, _args: &Vec<String>) -> CommandResult {
55     // Used so we can add options with no direct function
56     // e.g. help and quit
57     Ok(())
58 }
59 
60 pub struct CommandOption {
61     rules: Vec<String>,
62     description: String,
63     function_pointer: CommandFunction,
64 }
65 
66 /// Handles string command entered from command line.
67 pub(crate) struct CommandHandler {
68     context: Arc<Mutex<ClientContext>>,
69     command_options: HashMap<String, CommandOption>,
70 }
71 
72 /// Define what to do when a socket connects. Mainly for qualification purposes.
73 /// Specifically, after a socket is connected/accepted, we will do
74 /// (1) send a chunk of data every |send_interval| time until |num_frame| chunks has been sent.
75 /// (2) wait another |disconnect_delay| time. any incoming data will be dumpted during this time.
76 /// (3) disconnect the socket.
77 #[derive(Copy, Clone)]
78 pub struct SocketSchedule {
79     /// Number of times to send data
80     pub num_frame: u32,
81     /// Time interval between each sending
82     pub send_interval: Duration,
83     /// Extra time after the last sending. Any incoming data will be printed during this time.
84     pub disconnect_delay: Duration,
85 }
86 
87 struct DisplayList<T>(Vec<T>);
88 
89 impl<T: Display> Display for DisplayList<T> {
fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result90     fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
91         let _ = write!(f, "[\n");
92         for item in self.0.iter() {
93             let _ = write!(f, "  {}\n", item);
94         }
95 
96         write!(f, "]")
97     }
98 }
99 
wrap_help_text(text: &str, max: usize, indent: usize) -> String100 fn wrap_help_text(text: &str, max: usize, indent: usize) -> String {
101     let remaining_count = std::cmp::max(
102         // real_max
103         std::cmp::max(max, text.chars().count())
104         // take away char count
105          - text.chars().count()
106         // take away real_indent
107          - (
108              if std::cmp::max(max, text.chars().count())- text.chars().count() > indent {
109                  indent
110              } else {
111                  0
112              }),
113         0,
114     );
115 
116     format!("|{}{}{}|", INDENT_CHAR.repeat(indent), text, INDENT_CHAR.repeat(remaining_count))
117 }
118 
119 // This should be called during the constructor in order to populate the command option map
build_commands() -> HashMap<String, CommandOption>120 fn build_commands() -> HashMap<String, CommandOption> {
121     let mut command_options = HashMap::<String, CommandOption>::new();
122     command_options.insert(
123         String::from("adapter"),
124         CommandOption {
125             rules: vec![
126                 String::from("adapter enable"),
127                 String::from("adapter disable"),
128                 String::from("adapter show"),
129                 String::from("adapter discoverable <on|limited|off> <duration>"),
130                 String::from("adapter connectable <on|off>"),
131                 String::from("adapter set-name <name>"),
132             ],
133             description: String::from(
134                 "Enable/Disable/Show default bluetooth adapter. (e.g. adapter enable)\n
135                  Discoverable On/Limited/Off (e.g. adapter discoverable on 60)\n
136                  Connectable On/Off (e.g. adapter connectable on)",
137             ),
138             function_pointer: CommandHandler::cmd_adapter,
139         },
140     );
141     command_options.insert(
142         String::from("bond"),
143         CommandOption {
144             rules: vec![String::from("bond <add|remove|cancel> <address>")],
145             description: String::from("Creates a bond with a device."),
146             function_pointer: CommandHandler::cmd_bond,
147         },
148     );
149     command_options.insert(
150         String::from("device"),
151         CommandOption {
152             rules: vec![
153                 String::from("device <connect|disconnect|info> <address>"),
154                 String::from("device set-pairing-confirmation <address> <accept|reject>"),
155                 String::from("device set-pairing-pin <address> <pin|reject>"),
156                 String::from("device set-pairing-passkey <address> <passkey|reject>"),
157                 String::from("device set-alias <address> <new-alias>"),
158             ],
159             description: String::from("Take action on a remote device. (i.e. info)"),
160             function_pointer: CommandHandler::cmd_device,
161         },
162     );
163     command_options.insert(
164         String::from("discovery"),
165         CommandOption {
166             rules: vec![String::from("discovery <start|stop>")],
167             description: String::from("Start and stop device discovery. (e.g. discovery start)"),
168             function_pointer: CommandHandler::cmd_discovery,
169         },
170     );
171     command_options.insert(
172         String::from("floss"),
173         CommandOption {
174             rules: vec![String::from("floss <enable|disable>")],
175             description: String::from("Enable or disable Floss for dogfood."),
176             function_pointer: CommandHandler::cmd_floss,
177         },
178     );
179     command_options.insert(
180         String::from("gatt"),
181         CommandOption {
182             rules: vec![
183                 String::from("gatt register-client"),
184                 String::from("gatt client-connect <address>"),
185                 String::from("gatt client-read-phy <address>"),
186                 String::from("gatt client-discover-services <address>"),
187                 String::from("gatt client-discover-service-by-uuid-pts <address> <uuid>"),
188                 String::from("gatt client-disconnect <address>"),
189                 String::from("gatt configure-mtu <address> <mtu>"),
190                 String::from("gatt set-direct-connect <true|false>"),
191                 String::from("gatt set-connect-transport <Bredr|LE|Auto>"),
192                 String::from("gatt set-connect-opportunistic <true|false>"),
193                 String::from("gatt set-connect-phy <Phy1m|Phy2m|PhyCoded>"),
194                 String::from("gatt set-auth-req <NONE|EncNoMitm|EncMitm|SignedNoMitm|SignedMitm>"),
195                 String::from(
196                     "gatt write-characteristic <address> <handle> <NoRsp|Write|Prepare> <value>",
197                 ),
198                 String::from("gatt read-characteristic <address> <handle>"),
199                 String::from(
200                     "gatt read-characteristic-by-uuid <address> <uuid> <start_handle> <end_handle>",
201                 ),
202                 String::from("gatt register-notification <address> <handle> <enable|disable>"),
203                 String::from("gatt register-server"),
204             ],
205             description: String::from("GATT tools"),
206             function_pointer: CommandHandler::cmd_gatt,
207         },
208     );
209     command_options.insert(
210         String::from("le-scan"),
211         CommandOption {
212             rules: vec![
213                 String::from("le-scan register-scanner"),
214                 String::from("le-scan unregister-scanner <scanner-id>"),
215                 String::from("le-scan start-scan <scanner-id>"),
216                 String::from("le-scan stop-scan <scanner-id>"),
217             ],
218             description: String::from("LE scanning utilities."),
219             function_pointer: CommandHandler::cmd_le_scan,
220         },
221     );
222     command_options.insert(
223         String::from("advertise"),
224         CommandOption {
225             rules: vec![
226                 String::from("advertise <on|off|ext>"),
227                 String::from("advertise set-interval <ms>"),
228                 String::from("advertise set-scan-rsp <enable|disable>"),
229                 String::from("advertise set-raw-data <raw-adv-data> <adv-id>"),
230                 String::from("advertise set-connectable <on|off> <adv-id>"),
231             ],
232             description: String::from("Advertising utilities."),
233             function_pointer: CommandHandler::cmd_advertise,
234         },
235     );
236     command_options.insert(
237         String::from("sdp"),
238         CommandOption {
239             rules: vec![String::from("sdp search <address> <uuid>")],
240             description: String::from("Service Discovery Protocol utilities."),
241             function_pointer: CommandHandler::cmd_sdp,
242         },
243     );
244     command_options.insert(
245         String::from("socket"),
246         CommandOption {
247             rules: vec![
248                 String::from("socket listen <auth-required> <Bredr|LE>"),
249                 String::from("socket listen-rfcomm <scn>"),
250                 String::from("socket send-msc <dlci> <address>"),
251                 String::from(
252                     "socket connect <address> <l2cap|rfcomm> <psm|uuid> <auth-required> <Bredr|LE>",
253                 ),
254                 String::from("socket disconnect <socket_id>"),
255                 String::from("socket set-on-connect-schedule <send|resend|dump>"),
256             ],
257             description: String::from("Socket manager utilities."),
258             function_pointer: CommandHandler::cmd_socket,
259         },
260     );
261     command_options.insert(
262         String::from("hid"),
263         CommandOption {
264             rules: vec![
265                 String::from("hid get-report <address> <Input|Output|Feature> <report_id>"),
266                 String::from("hid set-report <address> <Input|Output|Feature> <report_value>"),
267                 String::from("hid send-data <address> <data>"),
268             ],
269             description: String::from("Socket manager utilities."),
270             function_pointer: CommandHandler::cmd_hid,
271         },
272     );
273     command_options.insert(
274         String::from("get-address"),
275         CommandOption {
276             rules: vec![String::from("get-address")],
277             description: String::from("Gets the local device address."),
278             function_pointer: CommandHandler::cmd_get_address,
279         },
280     );
281     command_options.insert(
282         String::from("qa"),
283         CommandOption {
284             rules: vec![String::from("qa add-media-player <name> <browsing_supported>")],
285             description: String::from("Methods for testing purposes"),
286             function_pointer: CommandHandler::cmd_qa,
287         },
288     );
289     command_options.insert(
290         String::from("help"),
291         CommandOption {
292             rules: vec![String::from("help")],
293             description: String::from("Shows this menu."),
294             function_pointer: CommandHandler::cmd_help,
295         },
296     );
297     command_options.insert(
298         String::from("list"),
299         CommandOption {
300             rules: vec![String::from("list <bonded|found|connected>")],
301             description: String::from(
302                 "List bonded or found remote devices. Use: list <bonded|found>",
303             ),
304             function_pointer: CommandHandler::cmd_list_devices,
305         },
306     );
307     command_options.insert(
308         String::from("telephony"),
309         CommandOption {
310             rules: vec![
311                 String::from("telephony set-network <on|off>"),
312                 String::from("telephony set-roaming <on|off>"),
313                 String::from("telephony set-signal <strength>"),
314                 String::from("telephony set-battery <level>"),
315                 String::from("telephony <enable|disable>"),
316                 String::from("telephony <incoming-call|dialing-call> <number>"),
317                 String::from("telephony <answer-call|hangup-call>"),
318                 String::from("telephony <set-memory-call|set-last-call> [<number>]"),
319                 String::from(
320                     "telephony <release-held|release-active-accept-held|hold-active-accept-held>",
321                 ),
322                 String::from("telephony <audio-connect|audio-disconnect> <address>"),
323             ],
324             description: String::from("Set device telephony status."),
325             function_pointer: CommandHandler::cmd_telephony,
326         },
327     );
328     command_options.insert(
329         String::from("quit"),
330         CommandOption {
331             rules: vec![String::from("quit")],
332             description: String::from("Quit out of the interactive shell."),
333             function_pointer: _noop,
334         },
335     );
336     command_options
337 }
338 
339 // Helper to index a vector safely. The same as `args.get(i)` but converts the None into a
340 // CommandError::InvalidArgs.
341 //
342 // Use this to safely index an argument and conveniently return the error if the argument does not
343 // exist.
get_arg<I>( args: &Vec<String>, index: I, ) -> Result<&<I as SliceIndex<[String]>>::Output, CommandError> where I: SliceIndex<[String]>,344 fn get_arg<I>(
345     args: &Vec<String>,
346     index: I,
347 ) -> Result<&<I as SliceIndex<[String]>>::Output, CommandError>
348 where
349     I: SliceIndex<[String]>,
350 {
351     args.get(index).ok_or(CommandError::InvalidArgs)
352 }
353 
354 impl CommandHandler {
355     /// Creates a new CommandHandler.
new(context: Arc<Mutex<ClientContext>>) -> CommandHandler356     pub fn new(context: Arc<Mutex<ClientContext>>) -> CommandHandler {
357         CommandHandler { context, command_options: build_commands() }
358     }
359 
360     /// Entry point for command and arguments
process_cmd_line(&mut self, command: &str, args: &Vec<String>)361     pub fn process_cmd_line(&mut self, command: &str, args: &Vec<String>) {
362         // Ignore empty line
363         match command {
364             "" => {}
365             _ => match self.command_options.get(command) {
366                 Some(cmd) => {
367                     let rules = cmd.rules.clone();
368                     match (cmd.function_pointer)(self, &args) {
369                         Ok(()) => {}
370                         Err(CommandError::InvalidArgs) => {
371                             print_error!("Invalid arguments. Usage:\n{}", rules.join("\n"));
372                         }
373                         Err(CommandError::Failed(msg)) => {
374                             print_error!("Command failed: {}", msg);
375                         }
376                     }
377                 }
378                 None => {
379                     println!("'{}' is an invalid command!", command);
380                     self.cmd_help(&args).ok();
381                 }
382             },
383         };
384     }
385 
lock_context(&self) -> std::sync::MutexGuard<ClientContext>386     fn lock_context(&self) -> std::sync::MutexGuard<ClientContext> {
387         self.context.lock().unwrap()
388     }
389 
390     // Common message for when the adapter isn't ready
adapter_not_ready(&self) -> CommandError391     fn adapter_not_ready(&self) -> CommandError {
392         format!(
393             "Default adapter {} is not enabled. Enable the adapter before using this command.",
394             self.lock_context().default_adapter
395         )
396         .into()
397     }
398 
cmd_help(&mut self, args: &Vec<String>) -> CommandResult399     fn cmd_help(&mut self, args: &Vec<String>) -> CommandResult {
400         if let Some(command) = args.get(0) {
401             match self.command_options.get(command) {
402                 Some(cmd) => {
403                     println!(
404                         "\n{}{}\n{}{}\n",
405                         INDENT_CHAR.repeat(4),
406                         command,
407                         INDENT_CHAR.repeat(8),
408                         cmd.description
409                     );
410                 }
411                 None => {
412                     println!("'{}' is an invalid command!", command);
413                     self.cmd_help(&vec![]).ok();
414                 }
415             }
416         } else {
417             // Build equals bar and Shave off sides
418             let equal_bar = format!(" {} ", BAR1_CHAR.repeat(MAX_MENU_CHAR_WIDTH));
419 
420             // Build empty bar and Shave off sides
421             let empty_bar = format!("|{}|", INDENT_CHAR.repeat(MAX_MENU_CHAR_WIDTH));
422 
423             // Header
424             println!(
425                 "\n{}\n{}\n{}\n{}",
426                 equal_bar,
427                 wrap_help_text("Help Menu", MAX_MENU_CHAR_WIDTH, 2),
428                 // Minus bar
429                 format!("+{}+", BAR2_CHAR.repeat(MAX_MENU_CHAR_WIDTH)),
430                 empty_bar
431             );
432 
433             // Print commands
434             for (key, val) in self.command_options.iter() {
435                 println!(
436                     "{}\n{}\n{}",
437                     wrap_help_text(&key, MAX_MENU_CHAR_WIDTH, 4),
438                     wrap_help_text(&val.description, MAX_MENU_CHAR_WIDTH, 8),
439                     empty_bar
440                 );
441             }
442 
443             // Footer
444             println!("{}\n{}", empty_bar, equal_bar);
445         }
446 
447         Ok(())
448     }
449 
cmd_adapter(&mut self, args: &Vec<String>) -> CommandResult450     fn cmd_adapter(&mut self, args: &Vec<String>) -> CommandResult {
451         if !self.lock_context().manager_dbus.get_floss_enabled() {
452             return Err("Floss is not enabled. First run, `floss enable`".into());
453         }
454 
455         let default_adapter = self.lock_context().default_adapter;
456 
457         let command = get_arg(args, 0)?;
458 
459         match &command[..] {
460             "enable" => {
461                 if self.lock_context().is_restricted {
462                     return Err("You are not allowed to toggle adapter power".into());
463                 }
464                 self.lock_context().manager_dbus.start(default_adapter);
465             }
466             "disable" => {
467                 if self.lock_context().is_restricted {
468                     return Err("You are not allowed to toggle adapter power".into());
469                 }
470                 self.lock_context().manager_dbus.stop(default_adapter);
471             }
472             "show" => {
473                 if !self.lock_context().adapter_ready {
474                     return Err(self.adapter_not_ready());
475                 }
476 
477                 let enabled = self.lock_context().enabled;
478                 let address = match self.lock_context().adapter_address.as_ref() {
479                     Some(x) => x.clone(),
480                     None => String::from(""),
481                 };
482                 let context = self.lock_context();
483                 let adapter_dbus = context.adapter_dbus.as_ref().unwrap();
484                 let qa_legacy_dbus = context.qa_legacy_dbus.as_ref().unwrap();
485                 let qa_dbus = context.qa_dbus.as_ref().unwrap();
486                 let name = adapter_dbus.get_name();
487                 let uuids = adapter_dbus.get_uuids();
488                 let is_discoverable = adapter_dbus.get_discoverable();
489                 let is_connectable = qa_legacy_dbus.get_connectable();
490                 let alias = qa_legacy_dbus.get_alias();
491                 let modalias = qa_legacy_dbus.get_modalias();
492                 let discoverable_mode = qa_dbus.get_discoverable_mode();
493                 let discoverable_timeout = adapter_dbus.get_discoverable_timeout();
494                 let cod = adapter_dbus.get_bluetooth_class();
495                 let multi_adv_supported = adapter_dbus.is_multi_advertisement_supported();
496                 let le_ext_adv_supported = adapter_dbus.is_le_extended_advertising_supported();
497                 let wbs_supported = adapter_dbus.is_wbs_supported();
498                 let supported_profiles = UuidHelper::get_supported_profiles();
499                 let connected_profiles: Vec<(Profile, ProfileConnectionState)> = supported_profiles
500                     .iter()
501                     .map(|&prof| {
502                         if let Some(uuid) = UuidHelper::get_profile_uuid(&prof) {
503                             (prof, adapter_dbus.get_profile_connection_state(uuid.clone()))
504                         } else {
505                             (prof, ProfileConnectionState::Disconnected)
506                         }
507                     })
508                     .filter(|(_prof, state)| state != &ProfileConnectionState::Disconnected)
509                     .collect();
510                 print_info!("Address: {}", address);
511                 print_info!("Name: {}", name);
512                 print_info!("Alias: {}", alias);
513                 print_info!("Modalias: {}", modalias);
514                 print_info!("State: {}", if enabled { "enabled" } else { "disabled" });
515                 print_info!("Discoverable: {}", is_discoverable);
516                 print_info!("Discoverable mode: {:?}", discoverable_mode);
517                 print_info!("DiscoverableTimeout: {}s", discoverable_timeout);
518                 print_info!("Connectable: {}", is_connectable);
519                 print_info!("Class: {:#06x}", cod);
520                 print_info!("IsMultiAdvertisementSupported: {}", multi_adv_supported);
521                 print_info!("IsLeExtendedAdvertisingSupported: {}", le_ext_adv_supported);
522                 print_info!("Connected profiles: {:?}", connected_profiles);
523                 print_info!("IsWbsSupported: {}", wbs_supported);
524                 print_info!(
525                     "Uuids: {}",
526                     DisplayList(
527                         uuids
528                             .iter()
529                             .map(|&x| UuidHelper::known_uuid_to_string(&x))
530                             .collect::<Vec<String>>()
531                     )
532                 );
533             }
534             "discoverable" => match &get_arg(args, 1)?[..] {
535                 "on" => {
536                     let duration = String::from(get_arg(args, 2)?)
537                         .parse::<u32>()
538                         .or(Err("Failed parsing duration."))?;
539 
540                     let discoverable = self
541                         .lock_context()
542                         .adapter_dbus
543                         .as_mut()
544                         .unwrap()
545                         .set_discoverable(BtDiscMode::GeneralDiscoverable, duration);
546                     print_info!(
547                         "Set discoverable for {} seconds: {}",
548                         duration,
549                         if discoverable { "succeeded" } else { "failed" }
550                     );
551                 }
552                 "limited" => {
553                     let duration = String::from(get_arg(args, 2)?)
554                         .parse::<u32>()
555                         .or(Err("Failed parsing duration."))?;
556 
557                     let discoverable = self
558                         .lock_context()
559                         .adapter_dbus
560                         .as_mut()
561                         .unwrap()
562                         .set_discoverable(BtDiscMode::LimitedDiscoverable, duration);
563                     print_info!(
564                         "Set limited discoverable for {} seconds: {}",
565                         duration,
566                         if discoverable { "succeeded" } else { "failed" }
567                     );
568                 }
569                 "off" => {
570                     let discoverable = self
571                         .lock_context()
572                         .adapter_dbus
573                         .as_mut()
574                         .unwrap()
575                         .set_discoverable(BtDiscMode::NonDiscoverable, 0 /*not used*/);
576                     print_info!(
577                         "Turn discoverable off: {}",
578                         if discoverable { "succeeded" } else { "failed" }
579                     );
580                 }
581                 other => println!("Invalid argument for adapter discoverable '{}'", other),
582             },
583             "connectable" => match &get_arg(args, 1)?[..] {
584                 "on" => {
585                     let ret =
586                         self.lock_context().qa_legacy_dbus.as_mut().unwrap().set_connectable(true);
587                     print_info!("Set connectable on {}", if ret { "succeeded" } else { "failed" });
588                 }
589                 "off" => {
590                     let ret =
591                         self.lock_context().qa_legacy_dbus.as_mut().unwrap().set_connectable(false);
592                     print_info!("Set connectable off {}", if ret { "succeeded" } else { "failed" });
593                 }
594                 other => println!("Invalid argument for adapter connectable '{}'", other),
595             },
596             "set-name" => {
597                 if let Some(name) = args.get(1) {
598                     self.lock_context().adapter_dbus.as_ref().unwrap().set_name(name.to_string());
599                 } else {
600                     println!("usage: adapter set-name <name>");
601                 }
602             }
603 
604             _ => return Err(CommandError::InvalidArgs),
605         };
606 
607         Ok(())
608     }
609 
cmd_get_address(&mut self, _args: &Vec<String>) -> CommandResult610     fn cmd_get_address(&mut self, _args: &Vec<String>) -> CommandResult {
611         if !self.lock_context().adapter_ready {
612             return Err(self.adapter_not_ready());
613         }
614 
615         let address = self.lock_context().update_adapter_address();
616         print_info!("Local address = {}", &address);
617         Ok(())
618     }
619 
cmd_discovery(&mut self, args: &Vec<String>) -> CommandResult620     fn cmd_discovery(&mut self, args: &Vec<String>) -> CommandResult {
621         if !self.lock_context().adapter_ready {
622             return Err(self.adapter_not_ready());
623         }
624 
625         let command = get_arg(args, 0)?;
626 
627         match &command[..] {
628             "start" => {
629                 self.lock_context().adapter_dbus.as_mut().unwrap().start_discovery();
630             }
631             "stop" => {
632                 self.lock_context().adapter_dbus.as_mut().unwrap().cancel_discovery();
633             }
634             _ => return Err(CommandError::InvalidArgs),
635         }
636 
637         Ok(())
638     }
639 
cmd_bond(&mut self, args: &Vec<String>) -> CommandResult640     fn cmd_bond(&mut self, args: &Vec<String>) -> CommandResult {
641         if !self.lock_context().adapter_ready {
642             return Err(self.adapter_not_ready());
643         }
644 
645         let command = get_arg(args, 0)?;
646 
647         match &command[..] {
648             "add" => {
649                 let device = BluetoothDevice {
650                     address: String::from(get_arg(args, 1)?),
651                     name: String::from("Classic Device"),
652                 };
653 
654                 let bonding_attempt = &self.lock_context().bonding_attempt.as_ref().cloned();
655 
656                 if bonding_attempt.is_some() {
657                     return Err(format!(
658                         "Already bonding [{}]. Cancel bonding first.",
659                         bonding_attempt.as_ref().unwrap().address,
660                     )
661                     .into());
662                 }
663 
664                 let success = self
665                     .lock_context()
666                     .adapter_dbus
667                     .as_mut()
668                     .unwrap()
669                     .create_bond(device.clone(), BtTransport::Auto);
670 
671                 if success {
672                     self.lock_context().bonding_attempt = Some(device);
673                 }
674             }
675             "remove" => {
676                 let device = BluetoothDevice {
677                     address: String::from(get_arg(args, 1)?),
678                     name: String::from("Classic Device"),
679                 };
680 
681                 self.lock_context().adapter_dbus.as_ref().unwrap().remove_bond(device);
682             }
683             "cancel" => {
684                 let device = BluetoothDevice {
685                     address: String::from(get_arg(args, 1)?),
686                     name: String::from("Classic Device"),
687                 };
688 
689                 self.lock_context().adapter_dbus.as_ref().unwrap().cancel_bond_process(device);
690             }
691             other => {
692                 println!("Invalid argument '{}'", other);
693             }
694         }
695 
696         Ok(())
697     }
698 
cmd_device(&mut self, args: &Vec<String>) -> CommandResult699     fn cmd_device(&mut self, args: &Vec<String>) -> CommandResult {
700         if !self.lock_context().adapter_ready {
701             return Err(self.adapter_not_ready());
702         }
703 
704         let command = &get_arg(args, 0)?;
705 
706         match &command[..] {
707             "connect" => {
708                 let device = BluetoothDevice {
709                     address: String::from(get_arg(args, 1)?),
710                     name: String::from("Classic Device"),
711                 };
712 
713                 let success = self
714                     .lock_context()
715                     .adapter_dbus
716                     .as_mut()
717                     .unwrap()
718                     .connect_all_enabled_profiles(device.clone());
719 
720                 if success {
721                     println!("Connecting to {}", &device.address);
722                 } else {
723                     println!("Can't connect to {}", &device.address);
724                 }
725             }
726             "disconnect" => {
727                 let device = BluetoothDevice {
728                     address: String::from(get_arg(args, 1)?),
729                     name: String::from("Classic Device"),
730                 };
731 
732                 let success = self
733                     .lock_context()
734                     .adapter_dbus
735                     .as_mut()
736                     .unwrap()
737                     .disconnect_all_enabled_profiles(device.clone());
738 
739                 if success {
740                     println!("Disconnecting from {}", &device.address);
741                 } else {
742                     println!("Can't disconnect from {}", &device.address);
743                 }
744             }
745             "info" => {
746                 let device = BluetoothDevice {
747                     address: String::from(get_arg(args, 1)?),
748                     name: String::from("Classic Device"),
749                 };
750 
751                 let (
752                     name,
753                     alias,
754                     device_type,
755                     class,
756                     appearance,
757                     bonded,
758                     connection_state,
759                     uuids,
760                     wake_allowed,
761                 ) = {
762                     let ctx = self.lock_context();
763                     let adapter = ctx.adapter_dbus.as_ref().unwrap();
764 
765                     let name = adapter.get_remote_name(device.clone());
766                     let device_type = adapter.get_remote_type(device.clone());
767                     let alias = adapter.get_remote_alias(device.clone());
768                     let class = adapter.get_remote_class(device.clone());
769                     let appearance = adapter.get_remote_appearance(device.clone());
770                     let bonded = adapter.get_bond_state(device.clone());
771                     let connection_state = match adapter.get_connection_state(device.clone()) {
772                         BtConnectionState::NotConnected => "Not Connected",
773                         BtConnectionState::ConnectedOnly => "Connected",
774                         _ => "Connected and Paired",
775                     };
776                     let uuids = adapter.get_remote_uuids(device.clone());
777                     let wake_allowed = adapter.get_remote_wake_allowed(device.clone());
778 
779                     (
780                         name,
781                         alias,
782                         device_type,
783                         class,
784                         appearance,
785                         bonded,
786                         connection_state,
787                         uuids,
788                         wake_allowed,
789                     )
790                 };
791 
792                 print_info!("Address: {}", &device.address);
793                 print_info!("Name: {}", name);
794                 print_info!("Alias: {}", alias);
795                 print_info!("Type: {:?}", device_type);
796                 print_info!("Class: {}", class);
797                 print_info!("Appearance: {}", appearance);
798                 print_info!("Wake Allowed: {}", wake_allowed);
799                 print_info!("Bond State: {:?}", bonded);
800                 print_info!("Connection State: {}", connection_state);
801                 print_info!(
802                     "Uuids: {}",
803                     DisplayList(
804                         uuids
805                             .iter()
806                             .map(|&x| UuidHelper::known_uuid_to_string(&x))
807                             .collect::<Vec<String>>()
808                     )
809                 );
810             }
811             "set-alias" => {
812                 let new_alias = get_arg(args, 2)?;
813                 let device = BluetoothDevice {
814                     address: String::from(get_arg(args, 1)?),
815                     name: String::from(""),
816                 };
817                 let old_alias = self
818                     .lock_context()
819                     .adapter_dbus
820                     .as_ref()
821                     .unwrap()
822                     .get_remote_alias(device.clone());
823                 println!(
824                     "Updating alias for {}: {} -> {}",
825                     get_arg(args, 1)?,
826                     old_alias,
827                     new_alias
828                 );
829                 self.lock_context()
830                     .adapter_dbus
831                     .as_mut()
832                     .unwrap()
833                     .set_remote_alias(device.clone(), new_alias.clone());
834             }
835             "set-pairing-confirmation" => {
836                 let device = BluetoothDevice {
837                     address: String::from(get_arg(args, 1)?),
838                     name: String::from(""),
839                 };
840                 let accept = match &get_arg(args, 2)?[..] {
841                     "accept" => true,
842                     "reject" => false,
843                     other => {
844                         return Err(format!("Failed to parse '{}'", other).into());
845                     }
846                 };
847 
848                 self.lock_context()
849                     .adapter_dbus
850                     .as_mut()
851                     .unwrap()
852                     .set_pairing_confirmation(device.clone(), accept);
853             }
854             "set-pairing-pin" => {
855                 let device = BluetoothDevice {
856                     address: String::from(get_arg(args, 1)?),
857                     name: String::from(""),
858                 };
859                 let pin = get_arg(args, 2)?;
860 
861                 let (accept, pin) = match (&pin[..], pin) {
862                     ("reject", _) => (false, vec![]),
863                     (_, p) => (true, p.as_bytes().iter().cloned().collect::<Vec<u8>>()),
864                 };
865 
866                 self.lock_context().adapter_dbus.as_mut().unwrap().set_pin(
867                     device.clone(),
868                     accept,
869                     pin,
870                 );
871             }
872             "set-pairing-passkey" => {
873                 let device = BluetoothDevice {
874                     address: String::from(get_arg(args, 1)?),
875                     name: String::from(""),
876                 };
877                 let passkey = get_arg(args, 2)?;
878                 let (accept, passkey) = match (&passkey[..], String::from(passkey).parse::<u32>()) {
879                     (_, Ok(p)) => (true, Vec::from(p.to_ne_bytes())),
880                     ("reject", _) => (false, vec![]),
881                     _ => {
882                         return Err(format!("Failed to parse '{}'", passkey).into());
883                     }
884                 };
885 
886                 self.lock_context().adapter_dbus.as_mut().unwrap().set_passkey(
887                     device.clone(),
888                     accept,
889                     passkey,
890                 );
891             }
892             other => {
893                 println!("Invalid argument '{}'", other);
894             }
895         }
896 
897         Ok(())
898     }
899 
cmd_floss(&mut self, args: &Vec<String>) -> CommandResult900     fn cmd_floss(&mut self, args: &Vec<String>) -> CommandResult {
901         let command = get_arg(args, 0)?;
902 
903         match &command[..] {
904             "enable" => {
905                 self.lock_context().manager_dbus.set_floss_enabled(true);
906             }
907             "disable" => {
908                 self.lock_context().manager_dbus.set_floss_enabled(false);
909             }
910             "show" => {
911                 print_info!(
912                     "Floss enabled: {}",
913                     self.lock_context().manager_dbus.get_floss_enabled()
914                 );
915             }
916             _ => return Err(CommandError::InvalidArgs),
917         }
918 
919         Ok(())
920     }
921 
cmd_gatt(&mut self, args: &Vec<String>) -> CommandResult922     fn cmd_gatt(&mut self, args: &Vec<String>) -> CommandResult {
923         if !self.lock_context().adapter_ready {
924             return Err(self.adapter_not_ready());
925         }
926 
927         let command = get_arg(args, 0)?;
928 
929         match &command[..] {
930             "register-client" => {
931                 let dbus_connection = self.lock_context().dbus_connection.clone();
932                 let dbus_crossroads = self.lock_context().dbus_crossroads.clone();
933 
934                 self.lock_context().gatt_dbus.as_mut().unwrap().register_client(
935                     String::from(GATT_CLIENT_APP_UUID),
936                     Box::new(BtGattCallback::new(
937                         String::from("/org/chromium/bluetooth/client/bluetooth_gatt_callback"),
938                         self.context.clone(),
939                         dbus_connection,
940                         dbus_crossroads,
941                     )),
942                     false,
943                 );
944             }
945             "client-connect" => {
946                 let client_id = self
947                     .lock_context()
948                     .gatt_client_context
949                     .client_id
950                     .ok_or("GATT client is not yet registered.")?;
951 
952                 let addr = String::from(get_arg(args, 1)?);
953                 let is_direct = self.lock_context().gatt_client_context.is_connect_direct;
954                 let transport = self.lock_context().gatt_client_context.connect_transport;
955                 let oppurtunistic = self.lock_context().gatt_client_context.connect_opportunistic;
956                 let phy = self.lock_context().gatt_client_context.connect_phy;
957 
958                 println!("Initiating GATT client connect. client_id: {}, addr: {}, is_direct: {}, transport: {:?}, oppurtunistic: {}, phy: {:?}", client_id, addr, is_direct, transport, oppurtunistic, phy);
959                 self.lock_context().gatt_dbus.as_ref().unwrap().client_connect(
960                     client_id,
961                     addr,
962                     is_direct,
963                     transport,
964                     oppurtunistic,
965                     phy,
966                 );
967             }
968             "client-disconnect" => {
969                 let client_id = self
970                     .lock_context()
971                     .gatt_client_context
972                     .client_id
973                     .ok_or("GATT client is not yet registered.")?;
974 
975                 let addr = String::from(get_arg(args, 1)?);
976                 self.lock_context().gatt_dbus.as_ref().unwrap().client_disconnect(client_id, addr);
977             }
978             "client-read-phy" => {
979                 let client_id = self
980                     .lock_context()
981                     .gatt_client_context
982                     .client_id
983                     .ok_or("GATT client is not yet registered.")?;
984                 let addr = String::from(get_arg(args, 1)?);
985                 self.lock_context().gatt_dbus.as_mut().unwrap().client_read_phy(client_id, addr);
986             }
987             "client-discover-services" => {
988                 let client_id = self
989                     .lock_context()
990                     .gatt_client_context
991                     .client_id
992                     .ok_or("GATT client is not yet registered.")?;
993 
994                 let addr = String::from(get_arg(args, 1)?);
995                 self.lock_context().gatt_dbus.as_ref().unwrap().discover_services(client_id, addr);
996             }
997             "client-discover-service-by-uuid-pts" => {
998                 let client_id = self
999                     .lock_context()
1000                     .gatt_client_context
1001                     .client_id
1002                     .ok_or("GATT client is not yet registered.")?;
1003                 let addr = String::from(get_arg(args, 1)?);
1004                 let uuid = String::from(get_arg(args, 2)?);
1005                 self.lock_context()
1006                     .gatt_dbus
1007                     .as_ref()
1008                     .unwrap()
1009                     .btif_gattc_discover_service_by_uuid(client_id, addr, uuid);
1010             }
1011             "configure-mtu" => {
1012                 let client_id = self
1013                     .lock_context()
1014                     .gatt_client_context
1015                     .client_id
1016                     .ok_or("GATT client is not yet registered.")?;
1017 
1018                 let addr = String::from(get_arg(args, 1)?);
1019                 let mtu =
1020                     String::from(get_arg(args, 2)?).parse::<i32>().or(Err("Failed parsing mtu"))?;
1021 
1022                 self.lock_context().gatt_dbus.as_ref().unwrap().configure_mtu(client_id, addr, mtu)
1023             }
1024             "set-direct-connect" => {
1025                 let is_direct = String::from(get_arg(args, 1)?)
1026                     .parse::<bool>()
1027                     .or(Err("Failed to parse is_direct"))?;
1028 
1029                 self.lock_context().gatt_client_context.is_connect_direct = is_direct;
1030             }
1031             "set-connect-transport" => {
1032                 let transport = match &get_arg(args, 1)?[..] {
1033                     "Bredr" => BtTransport::Bredr,
1034                     "LE" => BtTransport::Le,
1035                     "Auto" => BtTransport::Auto,
1036                     _ => {
1037                         return Err("Failed to parse transport".into());
1038                     }
1039                 };
1040                 self.lock_context().gatt_client_context.connect_transport = transport;
1041             }
1042             "set-connect-opportunistic" => {
1043                 let opportunistic = String::from(get_arg(args, 1)?)
1044                     .parse::<bool>()
1045                     .or(Err("Failed to parse opportunistic"))?;
1046 
1047                 self.lock_context().gatt_client_context.connect_opportunistic = opportunistic;
1048             }
1049             "set-connect-phy" => {
1050                 let phy = match &get_arg(args, 1)?[..] {
1051                     "Phy1m" => LePhy::Phy1m,
1052                     "Phy2m" => LePhy::Phy2m,
1053                     "PhyCoded" => LePhy::PhyCoded,
1054                     _ => {
1055                         return Err("Failed to parse phy".into());
1056                     }
1057                 };
1058 
1059                 self.lock_context().gatt_client_context.connect_phy = phy;
1060             }
1061             "set-auth-req" => {
1062                 let flag = match &get_arg(args, 1)?[..] {
1063                     "NONE" => AuthReq::NONE,
1064                     "EncNoMitm" => AuthReq::EncNoMitm,
1065                     "EncMitm" => AuthReq::EncMitm,
1066                     "SignedNoMitm" => AuthReq::SignedNoMitm,
1067                     "SignedMitm" => AuthReq::SignedMitm,
1068                     _ => {
1069                         return Err("Failed to parse auth-req".into());
1070                     }
1071                 };
1072 
1073                 self.lock_context().gatt_client_context.auth_req = flag;
1074                 println!("AuthReq: {:?}", self.lock_context().gatt_client_context.get_auth_req());
1075             }
1076             "write-characteristic" => {
1077                 let addr = String::from(get_arg(args, 1)?);
1078                 let handle = String::from(get_arg(args, 2)?)
1079                     .parse::<i32>()
1080                     .or(Err("Failed to parse handle"))?;
1081 
1082                 let write_type = match &get_arg(args, 3)?[..] {
1083                     "NoRsp" => GattWriteType::WriteNoRsp,
1084                     "Write" => GattWriteType::Write,
1085                     "Prepare" => GattWriteType::WritePrepare,
1086                     _ => {
1087                         return Err("Failed to parse write-type".into());
1088                     }
1089                 };
1090 
1091                 let value = hex::decode(&get_arg(args, 4)?).or(Err("Failed to parse value"))?;
1092 
1093                 let client_id = self
1094                     .lock_context()
1095                     .gatt_client_context
1096                     .client_id
1097                     .ok_or("GATT client is not yet registered.")?;
1098 
1099                 let auth_req = self.lock_context().gatt_client_context.get_auth_req().into();
1100 
1101                 self.lock_context()
1102                     .gatt_dbus
1103                     .as_ref()
1104                     .unwrap()
1105                     .write_characteristic(client_id, addr, handle, write_type, auth_req, value);
1106             }
1107             "read-characteristic" => {
1108                 let addr = String::from(get_arg(args, 1)?);
1109                 let handle = String::from(get_arg(args, 2)?)
1110                     .parse::<i32>()
1111                     .or(Err("Failed to parse handle"))?;
1112                 let client_id = self
1113                     .lock_context()
1114                     .gatt_client_context
1115                     .client_id
1116                     .ok_or("GATT client is not yet registered.")?;
1117 
1118                 let auth_req = self.lock_context().gatt_client_context.get_auth_req().into();
1119 
1120                 self.lock_context()
1121                     .gatt_dbus
1122                     .as_ref()
1123                     .unwrap()
1124                     .read_characteristic(client_id, addr, handle, auth_req);
1125             }
1126             "read-characteristic-by-uuid" => {
1127                 let addr = String::from(get_arg(args, 1)?);
1128                 let uuid = String::from(get_arg(args, 2)?);
1129                 let start_handle = String::from(get_arg(args, 3)?)
1130                     .parse::<i32>()
1131                     .or(Err("Failed to parse start handle"))?;
1132                 let end_handle = String::from(get_arg(args, 4)?)
1133                     .parse::<i32>()
1134                     .or(Err("Failed to parse end handle"))?;
1135 
1136                 let client_id = self
1137                     .lock_context()
1138                     .gatt_client_context
1139                     .client_id
1140                     .ok_or("GATT client is not yet registered.")?;
1141 
1142                 let auth_req = self.lock_context().gatt_client_context.get_auth_req().into();
1143 
1144                 self.lock_context().gatt_dbus.as_ref().unwrap().read_using_characteristic_uuid(
1145                     client_id,
1146                     addr,
1147                     uuid,
1148                     start_handle,
1149                     end_handle,
1150                     auth_req,
1151                 );
1152             }
1153             "register-notification" => {
1154                 let addr = String::from(get_arg(args, 1)?);
1155                 let handle = String::from(get_arg(args, 2)?)
1156                     .parse::<i32>()
1157                     .or(Err("Failed to parse handle"))?;
1158                 let enable = match &get_arg(args, 3)?[..] {
1159                     "enable" => true,
1160                     "disable" => false,
1161                     _ => {
1162                         return Err("Failed to parse enable".into());
1163                     }
1164                 };
1165 
1166                 let client_id = self
1167                     .lock_context()
1168                     .gatt_client_context
1169                     .client_id
1170                     .ok_or("GATT client is not yet registered.")?;
1171 
1172                 self.lock_context()
1173                     .gatt_dbus
1174                     .as_ref()
1175                     .unwrap()
1176                     .register_for_notification(client_id, addr, handle, enable);
1177             }
1178             "register-server" => {
1179                 let dbus_connection = self.lock_context().dbus_connection.clone();
1180                 let dbus_crossroads = self.lock_context().dbus_crossroads.clone();
1181 
1182                 self.lock_context().gatt_dbus.as_mut().unwrap().register_server(
1183                     String::from(GATT_SERVER_APP_UUID),
1184                     Box::new(BtGattServerCallback::new(
1185                         String::from(
1186                             "/org/chromium/bluetooth/client/bluetooth_gatt_server_callback",
1187                         ),
1188                         self.context.clone(),
1189                         dbus_connection,
1190                         dbus_crossroads,
1191                     )),
1192                     false,
1193                 );
1194             }
1195             _ => return Err(CommandError::InvalidArgs),
1196         }
1197         Ok(())
1198     }
1199 
cmd_le_scan(&mut self, args: &Vec<String>) -> CommandResult1200     fn cmd_le_scan(&mut self, args: &Vec<String>) -> CommandResult {
1201         if !self.lock_context().adapter_ready {
1202             return Err(self.adapter_not_ready());
1203         }
1204 
1205         let command = get_arg(args, 0)?;
1206 
1207         match &command[..] {
1208             "register-scanner" => {
1209                 let scanner_callback_id = self
1210                     .lock_context()
1211                     .scanner_callback_id
1212                     .ok_or("Cannot register scanner before registering scanner callback")?;
1213 
1214                 let uuid = self
1215                     .lock_context()
1216                     .gatt_dbus
1217                     .as_mut()
1218                     .unwrap()
1219                     .register_scanner(scanner_callback_id);
1220 
1221                 print_info!("Scanner to be registered with UUID = {}", UuidWrapper(&uuid));
1222             }
1223             "unregister-scanner" => {
1224                 let scanner_id = String::from(get_arg(args, 1)?)
1225                     .parse::<u8>()
1226                     .or(Err("Failed parsing scanner id"))?;
1227 
1228                 self.lock_context().gatt_dbus.as_mut().unwrap().unregister_scanner(scanner_id);
1229             }
1230             "start-scan" => {
1231                 let scanner_id = String::from(get_arg(args, 1)?)
1232                     .parse::<u8>()
1233                     .or(Err("Failed parsing scanner id"))?;
1234 
1235                 self.lock_context().gatt_dbus.as_mut().unwrap().start_scan(
1236                     scanner_id,
1237                     // TODO(b/254870159): Construct real settings and filters depending on
1238                     // command line options.
1239                     ScanSettings { interval: 0, window: 0, scan_type: ScanType::Active },
1240                     Some(btstack::bluetooth_gatt::ScanFilter {
1241                         rssi_high_threshold: 0,
1242                         rssi_low_threshold: 0,
1243                         rssi_low_timeout: 0,
1244                         rssi_sampling_period: 0,
1245                         condition: btstack::bluetooth_gatt::ScanFilterCondition::Patterns(vec![]),
1246                     }),
1247                 );
1248 
1249                 self.lock_context().active_scanner_ids.insert(scanner_id);
1250             }
1251             "stop-scan" => {
1252                 let scanner_id = String::from(get_arg(args, 1)?)
1253                     .parse::<u8>()
1254                     .or(Err("Failed parsing scanner id"))?;
1255 
1256                 self.lock_context().gatt_dbus.as_mut().unwrap().stop_scan(scanner_id);
1257                 self.lock_context().active_scanner_ids.remove(&scanner_id);
1258             }
1259             _ => return Err(CommandError::InvalidArgs),
1260         }
1261 
1262         Ok(())
1263     }
1264 
1265     // TODO(b/233128828): More options will be implemented to test BLE advertising.
1266     // Such as setting advertising parameters, starting multiple advertising sets, etc.
cmd_advertise(&mut self, args: &Vec<String>) -> CommandResult1267     fn cmd_advertise(&mut self, args: &Vec<String>) -> CommandResult {
1268         if !self.lock_context().adapter_ready {
1269             return Err(self.adapter_not_ready());
1270         }
1271 
1272         if self.lock_context().advertiser_callback_id == None {
1273             return Err("No advertiser callback registered".into());
1274         }
1275 
1276         let callback_id = self.lock_context().advertiser_callback_id.clone().unwrap();
1277 
1278         let command = get_arg(args, 0)?;
1279 
1280         match &command[..] {
1281             "on" => {
1282                 print_info!("Creating legacy advertising set...");
1283                 let s = AdvSet::new(true); // legacy advertising
1284                 AdvSet::start(self.context.clone(), s, callback_id);
1285             }
1286             "off" => {
1287                 AdvSet::stop_all(self.context.clone());
1288             }
1289             "ext" => {
1290                 print_info!("Creating extended advertising set...");
1291                 let s = AdvSet::new(false); // extended advertising
1292                 AdvSet::start(self.context.clone(), s, callback_id);
1293             }
1294             "set-interval" => {
1295                 let ms = String::from(get_arg(args, 1)?).parse::<i32>();
1296                 if !ms.is_ok() {
1297                     return Err("Failed parsing interval".into());
1298                 }
1299                 let interval = ms.unwrap() * 8 / 5; // in 0.625 ms.
1300 
1301                 let mut context = self.lock_context();
1302                 context.adv_sets.iter_mut().for_each(|(_, s)| s.params.interval = interval);
1303 
1304                 // To avoid borrowing context as mutable from an immutable borrow.
1305                 // Required information is collected in advance and then passed
1306                 // to the D-Bus call which requires a mutable borrow.
1307                 let advs: Vec<(_, _)> = context
1308                     .adv_sets
1309                     .iter()
1310                     .filter_map(|(_, s)| s.adv_id.map(|adv_id| (adv_id.clone(), s.params.clone())))
1311                     .collect();
1312                 for (adv_id, params) in advs {
1313                     print_info!("Setting advertising parameters for {}", adv_id);
1314                     context.gatt_dbus.as_mut().unwrap().set_advertising_parameters(adv_id, params);
1315                 }
1316             }
1317             "set-connectable" => {
1318                 let connectable = match &get_arg(args, 1)?[..] {
1319                     "on" => true,
1320                     "off" => false,
1321                     _ => false,
1322                 };
1323 
1324                 let adv_id = String::from(get_arg(args, 2)?)
1325                     .parse::<i32>()
1326                     .or(Err("Failed parsing adv_id"))?;
1327 
1328                 let mut context = self.context.lock().unwrap();
1329 
1330                 let advs: Vec<(_, _)> = context
1331                     .adv_sets
1332                     .iter_mut()
1333                     .filter_map(|(_, s)| {
1334                         if !(s.adv_id.map_or(false, |id| id == adv_id)) {
1335                             return None;
1336                         }
1337                         s.params.connectable = connectable;
1338                         Some((s.params.clone(), s.data.clone()))
1339                     })
1340                     .collect();
1341 
1342                 for (params, data) in advs {
1343                     print_info!("Setting advertising parameters for {}", adv_id);
1344                     context.gatt_dbus.as_mut().unwrap().set_advertising_parameters(adv_id, params);
1345 
1346                     // renew the flags
1347                     print_info!("Setting advertising data for {}", adv_id);
1348                     context.gatt_dbus.as_mut().unwrap().set_advertising_data(adv_id, data);
1349                 }
1350             }
1351             "set-scan-rsp" => {
1352                 let enable = match &get_arg(args, 1)?[..] {
1353                     "enable" => true,
1354                     "disable" => false,
1355                     _ => false,
1356                 };
1357 
1358                 let mut context = self.lock_context();
1359                 context.adv_sets.iter_mut().for_each(|(_, s)| s.params.scannable = enable);
1360 
1361                 let advs: Vec<(_, _, _)> = context
1362                     .adv_sets
1363                     .iter()
1364                     .filter_map(|(_, s)| {
1365                         s.adv_id
1366                             .map(|adv_id| (adv_id.clone(), s.params.clone(), s.scan_rsp.clone()))
1367                     })
1368                     .collect();
1369                 for (adv_id, params, scan_rsp) in advs {
1370                     print_info!("Setting scan response data for {}", adv_id);
1371                     context.gatt_dbus.as_mut().unwrap().set_scan_response_data(adv_id, scan_rsp);
1372                     print_info!("Setting parameters for {}", adv_id);
1373                     context.gatt_dbus.as_mut().unwrap().set_advertising_parameters(adv_id, params);
1374                 }
1375             }
1376             "set-raw-data" => {
1377                 let data = hex::decode(get_arg(args, 1)?).or(Err("Failed parsing data"))?;
1378 
1379                 let adv_id = String::from(get_arg(args, 2)?)
1380                     .parse::<i32>()
1381                     .or(Err("Failed parsing adv_id"))?;
1382 
1383                 let mut context = self.context.lock().unwrap();
1384                 if context
1385                     .adv_sets
1386                     .iter()
1387                     .find(|(_, s)| s.adv_id.map_or(false, |id| id == adv_id))
1388                     .is_none()
1389                 {
1390                     return Err("Failed to find advertising set".into());
1391                 }
1392 
1393                 print_info!("Setting advertising data for {}", adv_id);
1394                 context.gatt_dbus.as_mut().unwrap().set_raw_adv_data(adv_id, data);
1395             }
1396             _ => return Err(CommandError::InvalidArgs),
1397         }
1398 
1399         Ok(())
1400     }
1401 
cmd_sdp(&mut self, args: &Vec<String>) -> CommandResult1402     fn cmd_sdp(&mut self, args: &Vec<String>) -> CommandResult {
1403         if !self.lock_context().adapter_ready {
1404             return Err(self.adapter_not_ready());
1405         }
1406 
1407         let command = get_arg(args, 0)?;
1408 
1409         match &command[..] {
1410             "search" => {
1411                 let device = BluetoothDevice {
1412                     address: String::from(get_arg(args, 1)?),
1413                     name: String::from(""),
1414                 };
1415                 let uuid = match UuidHelper::parse_string(get_arg(args, 2)?) {
1416                     Some(uu) => uu.uu,
1417                     None => return Err(CommandError::Failed("Invalid UUID".into())),
1418                 };
1419                 let success =
1420                     self.lock_context().adapter_dbus.as_ref().unwrap().sdp_search(device, uuid);
1421                 if !success {
1422                     return Err("Unable to execute SDP search".into());
1423                 }
1424             }
1425             _ => return Err(CommandError::InvalidArgs),
1426         }
1427         Ok(())
1428     }
1429 
cmd_socket(&mut self, args: &Vec<String>) -> CommandResult1430     fn cmd_socket(&mut self, args: &Vec<String>) -> CommandResult {
1431         if !self.lock_context().adapter_ready {
1432             return Err(self.adapter_not_ready());
1433         }
1434 
1435         let callback_id = match self.lock_context().socket_manager_callback_id.clone() {
1436             Some(id) => id,
1437             None => {
1438                 return Err("No socket manager callback registered.".into());
1439             }
1440         };
1441 
1442         let command = get_arg(args, 0)?;
1443 
1444         match &command[..] {
1445             "set-on-connect-schedule" => {
1446                 let schedule = match &get_arg(args, 1)?[..] {
1447                     "send" => SocketSchedule {
1448                         num_frame: 1,
1449                         send_interval: Duration::from_millis(0),
1450                         disconnect_delay: Duration::from_secs(30),
1451                     },
1452                     "resend" => SocketSchedule {
1453                         num_frame: 3,
1454                         send_interval: Duration::from_millis(100),
1455                         disconnect_delay: Duration::from_secs(30),
1456                     },
1457                     "dump" => SocketSchedule {
1458                         num_frame: 0,
1459                         send_interval: Duration::from_millis(0),
1460                         disconnect_delay: Duration::from_secs(30),
1461                     },
1462                     _ => {
1463                         return Err("Failed to parse schedule".into());
1464                     }
1465                 };
1466 
1467                 self.context.lock().unwrap().socket_test_schedule = Some(schedule);
1468             }
1469             "send-msc" => {
1470                 let dlci =
1471                     String::from(get_arg(args, 1)?).parse::<u8>().or(Err("Failed parsing DLCI"))?;
1472                 let addr = String::from(get_arg(args, 2)?);
1473                 self.context.lock().unwrap().qa_dbus.as_mut().unwrap().rfcomm_send_msc(dlci, addr);
1474             }
1475             "listen-rfcomm" => {
1476                 let scn = String::from(get_arg(args, 1)?)
1477                     .parse::<i32>()
1478                     .or(Err("Failed parsing Service Channel Number"))?;
1479                 let SocketResult { status, id } = self
1480                     .context
1481                     .lock()
1482                     .unwrap()
1483                     .socket_manager_dbus
1484                     .as_mut()
1485                     .unwrap()
1486                     .listen_using_rfcomm(callback_id, Some(scn), None, None, None);
1487                 if status != BtStatus::Success {
1488                     return Err(format!(
1489                         "Failed to request for listening using rfcomm, status = {:?}",
1490                         status,
1491                     )
1492                     .into());
1493                 }
1494                 print_info!("Requested for listening using rfcomm on socket {}", id);
1495             }
1496             "listen" => {
1497                 let auth_required = String::from(get_arg(args, 1)?)
1498                     .parse::<bool>()
1499                     .or(Err("Failed to parse auth-required"))?;
1500                 let is_le = match &get_arg(args, 2)?[..] {
1501                     "LE" => true,
1502                     "Bredr" => false,
1503                     _ => {
1504                         return Err("Failed to parse socket type".into());
1505                     }
1506                 };
1507 
1508                 let SocketResult { status, id } = {
1509                     let mut context_proxy = self.context.lock().unwrap();
1510                     let proxy = context_proxy.socket_manager_dbus.as_mut().unwrap();
1511                     if auth_required {
1512                         if is_le {
1513                             proxy.listen_using_l2cap_le_channel(callback_id)
1514                         } else {
1515                             proxy.listen_using_l2cap_channel(callback_id)
1516                         }
1517                     } else {
1518                         if is_le {
1519                             proxy.listen_using_insecure_l2cap_le_channel(callback_id)
1520                         } else {
1521                             proxy.listen_using_insecure_l2cap_channel(callback_id)
1522                         }
1523                     }
1524                 };
1525 
1526                 if status != BtStatus::Success {
1527                     return Err(format!(
1528                         "Failed to request for listening using l2cap channel, status = {:?}",
1529                         status,
1530                     )
1531                     .into());
1532                 }
1533                 print_info!("Requested for listening using l2cap channel on socket {}", id);
1534             }
1535             "connect" => {
1536                 let (addr, sock_type, psm_or_uuid) =
1537                     (&get_arg(args, 1)?, &get_arg(args, 2)?, &get_arg(args, 3)?);
1538                 let device = BluetoothDevice {
1539                     address: addr.clone().into(),
1540                     name: String::from("Socket Connect Device"),
1541                 };
1542 
1543                 let auth_required = String::from(get_arg(args, 4)?)
1544                     .parse::<bool>()
1545                     .or(Err("Failed to parse auth-required"))?;
1546 
1547                 let is_le = match &get_arg(args, 5)?[..] {
1548                     "LE" => true,
1549                     "Bredr" => false,
1550                     _ => {
1551                         return Err("Failed to parse socket type".into());
1552                     }
1553                 };
1554 
1555                 let SocketResult { status, id } = {
1556                     let mut context_proxy = self.context.lock().unwrap();
1557                     let proxy = context_proxy.socket_manager_dbus.as_mut().unwrap();
1558 
1559                     match &sock_type[0..] {
1560                         "l2cap" => {
1561                             let psm = match psm_or_uuid.clone().parse::<i32>() {
1562                                 Ok(v) => v,
1563                                 Err(e) => {
1564                                     return Err(CommandError::Failed(format!(
1565                                         "Bad PSM given. Error={}",
1566                                         e
1567                                     )));
1568                                 }
1569                             };
1570 
1571                             if auth_required {
1572                                 if is_le {
1573                                     proxy.create_l2cap_le_channel(callback_id, device, psm)
1574                                 } else {
1575                                     proxy.create_l2cap_channel(callback_id, device, psm)
1576                                 }
1577                             } else {
1578                                 if is_le {
1579                                     proxy.create_insecure_l2cap_le_channel(callback_id, device, psm)
1580                                 } else {
1581                                     proxy.create_insecure_l2cap_channel(callback_id, device, psm)
1582                                 }
1583                             }
1584                         }
1585                         "rfcomm" => {
1586                             let uuid = match UuidHelper::parse_string(psm_or_uuid.clone()) {
1587                                 Some(uu) => uu,
1588                                 None => {
1589                                     return Err(CommandError::Failed(format!(
1590                                         "Could not parse given uuid."
1591                                     )));
1592                                 }
1593                             };
1594 
1595                             if auth_required {
1596                                 proxy.create_rfcomm_socket_to_service_record(
1597                                     callback_id,
1598                                     device,
1599                                     uuid,
1600                                 )
1601                             } else {
1602                                 proxy.create_insecure_rfcomm_socket_to_service_record(
1603                                     callback_id,
1604                                     device,
1605                                     uuid,
1606                                 )
1607                             }
1608                         }
1609                         _ => {
1610                             return Err(CommandError::Failed(format!(
1611                                 "Unknown socket type: {}",
1612                                 sock_type
1613                             )));
1614                         }
1615                     }
1616                 };
1617 
1618                 if status != BtStatus::Success {
1619                     return Err(CommandError::Failed(format!("Failed to create socket with status={:?} against {}, type {}, with psm/uuid {}",
1620                         status, addr, sock_type, psm_or_uuid)));
1621                 } else {
1622                     print_info!("Called create socket with result ({:?}, {}) against {}, type {}, with psm/uuid {}",
1623                     status, id, addr, sock_type, psm_or_uuid);
1624                 }
1625             }
1626 
1627             _ => return Err(CommandError::InvalidArgs),
1628         };
1629 
1630         Ok(())
1631     }
1632 
cmd_hid(&mut self, args: &Vec<String>) -> CommandResult1633     fn cmd_hid(&mut self, args: &Vec<String>) -> CommandResult {
1634         if !self.context.lock().unwrap().adapter_ready {
1635             return Err(self.adapter_not_ready());
1636         }
1637 
1638         let command = get_arg(args, 0)?;
1639 
1640         match &command[..] {
1641             "get-report" => {
1642                 let addr = String::from(get_arg(args, 1)?);
1643                 let report_type = match &get_arg(args, 2)?[..] {
1644                     "Input" => BthhReportType::InputReport,
1645                     "Output" => BthhReportType::OutputReport,
1646                     "Feature" => BthhReportType::FeatureReport,
1647                     _ => {
1648                         return Err("Failed to parse report type".into());
1649                     }
1650                 };
1651                 let report_id = String::from(get_arg(args, 3)?)
1652                     .parse::<u8>()
1653                     .or(Err("Failed parsing report_id"))?;
1654 
1655                 self.context.lock().unwrap().qa_legacy_dbus.as_mut().unwrap().get_hid_report(
1656                     addr,
1657                     report_type,
1658                     report_id,
1659                 );
1660             }
1661             "set-report" => {
1662                 let addr = String::from(get_arg(args, 1)?);
1663                 let report_type = match &get_arg(args, 2)?[..] {
1664                     "Input" => BthhReportType::InputReport,
1665                     "Output" => BthhReportType::OutputReport,
1666                     "Feature" => BthhReportType::FeatureReport,
1667                     _ => {
1668                         return Err("Failed to parse report type".into());
1669                     }
1670                 };
1671                 let report_value = String::from(get_arg(args, 3)?);
1672 
1673                 self.context.lock().unwrap().qa_legacy_dbus.as_mut().unwrap().set_hid_report(
1674                     addr,
1675                     report_type,
1676                     report_value,
1677                 );
1678             }
1679             "send-data" => {
1680                 let addr = String::from(get_arg(args, 1)?);
1681                 let data = String::from(get_arg(args, 2)?);
1682 
1683                 self.context
1684                     .lock()
1685                     .unwrap()
1686                     .qa_legacy_dbus
1687                     .as_mut()
1688                     .unwrap()
1689                     .send_hid_data(addr, data);
1690             }
1691             _ => return Err(CommandError::InvalidArgs),
1692         };
1693 
1694         Ok(())
1695     }
1696 
1697     /// Get the list of rules of supported commands
get_command_rule_list(&self) -> Vec<String>1698     pub fn get_command_rule_list(&self) -> Vec<String> {
1699         self.command_options.values().flat_map(|cmd| cmd.rules.clone()).collect()
1700     }
1701 
cmd_list_devices(&mut self, args: &Vec<String>) -> CommandResult1702     fn cmd_list_devices(&mut self, args: &Vec<String>) -> CommandResult {
1703         if !self.lock_context().adapter_ready {
1704             return Err(self.adapter_not_ready());
1705         }
1706 
1707         let command = get_arg(args, 0)?;
1708 
1709         match &command[..] {
1710             "bonded" => {
1711                 print_info!("Known bonded devices:");
1712                 let devices =
1713                     self.lock_context().adapter_dbus.as_ref().unwrap().get_bonded_devices();
1714                 for device in devices.iter() {
1715                     print_info!("[{:17}] {}", device.address, device.name);
1716                 }
1717             }
1718             "found" => {
1719                 print_info!("Devices found in most recent discovery session:");
1720                 for (key, val) in self.lock_context().found_devices.iter() {
1721                     print_info!("[{:17}] {}", key, val.name);
1722                 }
1723             }
1724             "connected" => {
1725                 print_info!("Connected devices:");
1726                 let devices =
1727                     self.lock_context().adapter_dbus.as_ref().unwrap().get_connected_devices();
1728                 for device in devices.iter() {
1729                     print_info!("[{:17}] {}", device.address, device.name);
1730                 }
1731             }
1732             other => {
1733                 println!("Invalid argument '{}'", other);
1734             }
1735         }
1736 
1737         Ok(())
1738     }
1739 
cmd_telephony(&mut self, args: &Vec<String>) -> CommandResult1740     fn cmd_telephony(&mut self, args: &Vec<String>) -> CommandResult {
1741         if !self.context.lock().unwrap().adapter_ready {
1742             return Err(self.adapter_not_ready());
1743         }
1744 
1745         match &get_arg(args, 0)?[..] {
1746             "set-network" => {
1747                 self.context
1748                     .lock()
1749                     .unwrap()
1750                     .telephony_dbus
1751                     .as_mut()
1752                     .unwrap()
1753                     .set_network_available(match &get_arg(args, 1)?[..] {
1754                         "on" => true,
1755                         "off" => false,
1756                         other => {
1757                             return Err(format!("Invalid argument '{}'", other).into());
1758                         }
1759                     });
1760             }
1761             "set-roaming" => {
1762                 self.context.lock().unwrap().telephony_dbus.as_mut().unwrap().set_roaming(
1763                     match &get_arg(args, 1)?[..] {
1764                         "on" => true,
1765                         "off" => false,
1766                         other => {
1767                             return Err(format!("Invalid argument '{}'", other).into());
1768                         }
1769                     },
1770                 );
1771             }
1772             "set-signal" => {
1773                 let strength = String::from(get_arg(args, 1)?)
1774                     .parse::<i32>()
1775                     .or(Err("Failed parsing signal strength"))?;
1776                 if strength < 0 || strength > 5 {
1777                     return Err(
1778                         format!("Invalid signal strength, got {}, want 0 to 5", strength).into()
1779                     );
1780                 }
1781                 self.context
1782                     .lock()
1783                     .unwrap()
1784                     .telephony_dbus
1785                     .as_mut()
1786                     .unwrap()
1787                     .set_signal_strength(strength);
1788             }
1789             "set-battery" => {
1790                 let level = String::from(get_arg(args, 1)?)
1791                     .parse::<i32>()
1792                     .or(Err("Failed parsing battery level"))?;
1793                 if level < 0 || level > 5 {
1794                     return Err(format!("Invalid battery level, got {}, want 0 to 5", level).into());
1795                 }
1796                 self.context
1797                     .lock()
1798                     .unwrap()
1799                     .telephony_dbus
1800                     .as_mut()
1801                     .unwrap()
1802                     .set_battery_level(level);
1803             }
1804             "enable" => {
1805                 let mut context = self.lock_context();
1806                 context.telephony_dbus.as_mut().unwrap().set_phone_ops_enabled(true);
1807                 if context.mps_sdp_handle.is_none() {
1808                     let success = context
1809                         .adapter_dbus
1810                         .as_mut()
1811                         .unwrap()
1812                         .create_sdp_record(BtSdpRecord::Mps(BtSdpMpsRecord::default()));
1813                     if !success {
1814                         return Err(format!("Failed to create SDP record").into());
1815                     }
1816                 }
1817             }
1818             "disable" => {
1819                 let mut context = self.lock_context();
1820                 context.telephony_dbus.as_mut().unwrap().set_phone_ops_enabled(false);
1821                 if let Some(handle) = context.mps_sdp_handle.take() {
1822                     let success = context.adapter_dbus.as_mut().unwrap().remove_sdp_record(handle);
1823                     if !success {
1824                         return Err(format!("Failed to remove SDP record").into());
1825                     }
1826                 }
1827             }
1828             "incoming-call" => {
1829                 let success = self
1830                     .context
1831                     .lock()
1832                     .unwrap()
1833                     .telephony_dbus
1834                     .as_mut()
1835                     .unwrap()
1836                     .incoming_call(String::from(get_arg(args, 1)?));
1837                 if !success {
1838                     return Err("IncomingCall failed".into());
1839                 }
1840             }
1841             "dialing-call" => {
1842                 let success = self
1843                     .context
1844                     .lock()
1845                     .unwrap()
1846                     .telephony_dbus
1847                     .as_mut()
1848                     .unwrap()
1849                     .dialing_call(String::from(get_arg(args, 1)?));
1850                 if !success {
1851                     return Err("DialingCall failed".into());
1852                 }
1853             }
1854             "answer-call" => {
1855                 let success =
1856                     self.context.lock().unwrap().telephony_dbus.as_mut().unwrap().answer_call();
1857                 if !success {
1858                     return Err("AnswerCall failed".into());
1859                 }
1860             }
1861             "hangup-call" => {
1862                 let success =
1863                     self.context.lock().unwrap().telephony_dbus.as_mut().unwrap().hangup_call();
1864                 if !success {
1865                     return Err("HangupCall failed".into());
1866                 }
1867             }
1868             "set-memory-call" => {
1869                 let success = self
1870                     .context
1871                     .lock()
1872                     .unwrap()
1873                     .telephony_dbus
1874                     .as_mut()
1875                     .unwrap()
1876                     .set_memory_call(get_arg(args, 1).ok().map(String::from));
1877                 if !success {
1878                     return Err("SetMemoryCall failed".into());
1879                 }
1880             }
1881             "set-last-call" => {
1882                 let success = self
1883                     .context
1884                     .lock()
1885                     .unwrap()
1886                     .telephony_dbus
1887                     .as_mut()
1888                     .unwrap()
1889                     .set_last_call(get_arg(args, 1).ok().map(String::from));
1890                 if !success {
1891                     return Err("SetLastCall failed".into());
1892                 }
1893             }
1894             "release-held" => {
1895                 let success =
1896                     self.context.lock().unwrap().telephony_dbus.as_mut().unwrap().release_held();
1897                 if !success {
1898                     return Err("ReleaseHeld failed".into());
1899                 }
1900             }
1901             "release-active-accept-held" => {
1902                 let success = self
1903                     .context
1904                     .lock()
1905                     .unwrap()
1906                     .telephony_dbus
1907                     .as_mut()
1908                     .unwrap()
1909                     .release_active_accept_held();
1910                 if !success {
1911                     return Err("ReleaseActiveAcceptHeld failed".into());
1912                 }
1913             }
1914             "hold-active-accept-held" => {
1915                 let success = self
1916                     .context
1917                     .lock()
1918                     .unwrap()
1919                     .telephony_dbus
1920                     .as_mut()
1921                     .unwrap()
1922                     .hold_active_accept_held();
1923                 if !success {
1924                     return Err("HoldActiveAcceptHeld failed".into());
1925                 }
1926             }
1927             "audio-connect" => {
1928                 let success = self
1929                     .context
1930                     .lock()
1931                     .unwrap()
1932                     .telephony_dbus
1933                     .as_mut()
1934                     .unwrap()
1935                     .audio_connect(String::from(get_arg(args, 1)?));
1936                 if !success {
1937                     return Err("ConnectAudio failed".into());
1938                 }
1939             }
1940             "audio-disconnect" => {
1941                 self.context
1942                     .lock()
1943                     .unwrap()
1944                     .telephony_dbus
1945                     .as_mut()
1946                     .unwrap()
1947                     .audio_disconnect(String::from(get_arg(args, 1)?));
1948             }
1949             other => {
1950                 return Err(format!("Invalid argument '{}'", other).into());
1951             }
1952         }
1953         Ok(())
1954     }
1955 
cmd_qa(&mut self, args: &Vec<String>) -> CommandResult1956     fn cmd_qa(&mut self, args: &Vec<String>) -> CommandResult {
1957         if !self.context.lock().unwrap().adapter_ready {
1958             return Err(self.adapter_not_ready());
1959         }
1960 
1961         let command = get_arg(args, 0)?;
1962 
1963         match &command[..] {
1964             "add-media-player" => {
1965                 let name = String::from(get_arg(args, 1)?);
1966                 let browsing_supported = String::from(get_arg(args, 2)?)
1967                     .parse::<bool>()
1968                     .or(Err("Failed to parse browsing_supported"))?;
1969                 self.context
1970                     .lock()
1971                     .unwrap()
1972                     .qa_dbus
1973                     .as_mut()
1974                     .unwrap()
1975                     .add_media_player(name, browsing_supported);
1976             }
1977             _ => return Err(CommandError::InvalidArgs),
1978         };
1979 
1980         Ok(())
1981     }
1982 }
1983 
1984 #[cfg(test)]
1985 mod tests {
1986 
1987     use super::*;
1988 
1989     #[test]
test_wrap_help_text()1990     fn test_wrap_help_text() {
1991         let text = "hello";
1992         let text_len = text.chars().count();
1993         // ensure no overflow
1994         assert_eq!(format!("|{}|", text), wrap_help_text(text, 4, 0));
1995         assert_eq!(format!("|{}|", text), wrap_help_text(text, 5, 0));
1996         assert_eq!(format!("|{}{}|", text, " "), wrap_help_text(text, 6, 0));
1997         assert_eq!(format!("|{}{}|", text, " ".repeat(2)), wrap_help_text(text, 7, 0));
1998         assert_eq!(
1999             format!("|{}{}|", text, " ".repeat(100 - text_len)),
2000             wrap_help_text(text, 100, 0)
2001         );
2002         assert_eq!(format!("|{}{}|", " ", text), wrap_help_text(text, 4, 1));
2003         assert_eq!(format!("|{}{}|", " ".repeat(2), text), wrap_help_text(text, 5, 2));
2004         assert_eq!(format!("|{}{}{}|", " ".repeat(3), text, " "), wrap_help_text(text, 6, 3));
2005         assert_eq!(
2006             format!("|{}{}{}|", " ".repeat(4), text, " ".repeat(7 - text_len)),
2007             wrap_help_text(text, 7, 4)
2008         );
2009         assert_eq!(format!("|{}{}|", " ".repeat(9), text), wrap_help_text(text, 4, 9));
2010         assert_eq!(format!("|{}{}|", " ".repeat(10), text), wrap_help_text(text, 3, 10));
2011         assert_eq!(format!("|{}{}|", " ".repeat(11), text), wrap_help_text(text, 2, 11));
2012         assert_eq!(format!("|{}{}|", " ".repeat(12), text), wrap_help_text(text, 1, 12));
2013         assert_eq!("||", wrap_help_text("", 0, 0));
2014         assert_eq!("| |", wrap_help_text("", 1, 0));
2015         assert_eq!("|  |", wrap_help_text("", 1, 1));
2016         assert_eq!("| |", wrap_help_text("", 0, 1));
2017     }
2018 }
2019