• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2022 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 use anyhow::Result;
16 use clap::builder::{PossibleValue, TypedValueParser};
17 use clap::{Args, Parser, Subcommand, ValueEnum};
18 use hex::{decode as hex_to_bytes, FromHexError};
19 use netsim_proto::model::chip::ble_beacon::advertise_settings::{
20     AdvertiseMode as AdvertiseModeProto, AdvertiseTxPower as AdvertiseTxPowerProto,
21     Interval as IntervalProto, Tx_power as TxPowerProto,
22 };
23 use netsim_proto::model::chip::ble_beacon::{
24     AdvertiseData as AdvertiseDataProto, AdvertiseSettings as AdvertiseSettingsProto,
25 };
26 
27 use std::fmt;
28 use std::iter;
29 use std::str::FromStr;
30 
31 #[derive(Debug, Parser)]
32 pub struct NetsimArgs {
33     #[command(subcommand)]
34     pub command: Command,
35     /// Set verbose mode
36     #[arg(short, long)]
37     pub verbose: bool,
38     /// Set custom grpc port
39     #[arg(short, long)]
40     pub port: Option<i32>,
41     /// Set netsimd instance number to connect
42     #[arg(short, long)]
43     pub instance: Option<u16>,
44     /// Set vsock cid:port pair
45     #[arg(long)]
46     pub vsock: Option<String>,
47 }
48 
49 #[derive(Debug, Subcommand, PartialEq)]
50 #[command(infer_subcommands = true)]
51 pub enum Command {
52     /// Print Netsim version information
53     Version,
54     /// Control the radio state of a device
55     Radio(Radio),
56     /// Set the device location
57     Move(Move),
58     /// Display device(s) information
59     Devices(Devices),
60     /// Reset Netsim device scene
61     Reset,
62     /// Open netsim Web UI
63     Gui,
64     /// Control the packet capture functionalities with commands: list, patch, get
65     #[command(subcommand, visible_alias("pcap"))]
66     Capture(Capture),
67     /// Opens netsim artifacts directory (log, pcaps)
68     Artifact,
69     /// A chip that sends advertisements at a set interval
70     #[command(subcommand)]
71     Beacon(Beacon),
72     /// Open Bumble Hive Web Page
73     Bumble,
74 }
75 
76 #[derive(Debug, Args, PartialEq)]
77 pub struct Radio {
78     /// Radio type
79     #[arg(value_enum, ignore_case = true)]
80     pub radio_type: RadioType,
81     /// Radio status
82     #[arg(value_enum, ignore_case = true)]
83     pub status: UpDownStatus,
84     /// Device name
85     pub name: String,
86 }
87 
88 #[derive(Copy, Clone, Debug, PartialEq, Eq, ValueEnum)]
89 pub enum RadioType {
90     Ble,
91     Classic,
92     Wifi,
93     Uwb,
94 }
95 
96 impl fmt::Display for RadioType {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result97     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
98         write!(f, "{:?}", self)
99     }
100 }
101 
102 #[derive(Copy, Clone, Debug, PartialEq, Eq, ValueEnum)]
103 pub enum UpDownStatus {
104     Up,
105     Down,
106 }
107 
108 impl fmt::Display for UpDownStatus {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result109     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
110         write!(f, "{:?}", self)
111     }
112 }
113 
114 #[derive(Debug, Args, PartialEq)]
115 pub struct Move {
116     /// Device name
117     pub name: String,
118     /// x position of device
119     pub x: f32,
120     /// y position of device
121     pub y: f32,
122     /// Optional z position of device
123     pub z: Option<f32>,
124 }
125 
126 #[derive(Debug, Args, PartialEq)]
127 pub struct Devices {
128     /// Continuously print device(s) information every second
129     #[arg(short, long)]
130     pub continuous: bool,
131 }
132 
133 #[derive(Copy, Clone, Debug, PartialEq, Eq, ValueEnum, Default)]
134 pub enum OnOffState {
135     #[default]
136     On,
137     Off,
138 }
139 
140 #[derive(Debug, Subcommand, PartialEq)]
141 pub enum Beacon {
142     /// Create a beacon chip
143     #[command(subcommand)]
144     Create(BeaconCreate),
145     /// Modify a beacon chip
146     #[command(subcommand)]
147     Patch(BeaconPatch),
148     /// Remove a beacon chip
149     Remove(BeaconRemove),
150 }
151 
152 #[derive(Debug, Subcommand, PartialEq)]
153 pub enum BeaconCreate {
154     /// Create a Bluetooth low-energy beacon chip
155     Ble(BeaconCreateBle),
156 }
157 
158 #[derive(Debug, Args, PartialEq, Default)]
159 pub struct BeaconCreateBle {
160     /// Name of the device to create
161     pub device_name: Option<String>,
162     /// Name of the beacon chip to create within the new device. May only be specified if device_name is specified
163     pub chip_name: Option<String>,
164     /// Bluetooth address of the beacon. Must be a 6-byte hexadecimal string with each byte separated by a colon. Will be generated if not provided
165     #[arg(long)]
166     pub address: Option<String>,
167     #[command(flatten)]
168     pub settings: BeaconBleSettings,
169     #[command(flatten)]
170     pub advertise_data: BeaconBleAdvertiseData,
171     #[command(flatten)]
172     pub scan_response_data: BeaconBleScanResponseData,
173 }
174 
175 #[derive(Debug, Subcommand, PartialEq)]
176 pub enum BeaconPatch {
177     /// Modify a Bluetooth low-energy beacon chip
178     Ble(BeaconPatchBle),
179 }
180 
181 #[derive(Debug, Args, PartialEq, Default)]
182 pub struct BeaconPatchBle {
183     /// Name of the device that contains the chip
184     pub device_name: String,
185     /// Name of the beacon chip to modify
186     pub chip_name: String,
187     /// Bluetooth address of the beacon. Must be a 6-byte hexadecimal string with each byte separated by a colon
188     #[arg(long)]
189     pub address: Option<String>,
190     #[command(flatten)]
191     pub settings: BeaconBleSettings,
192     #[command(flatten)]
193     pub advertise_data: BeaconBleAdvertiseData,
194     #[command(flatten)]
195     pub scan_response_data: BeaconBleScanResponseData,
196 }
197 
198 #[derive(Debug, Args, PartialEq)]
199 pub struct BeaconRemove {
200     /// Name of the device to remove
201     pub device_name: String,
202     /// Name of the beacon chip to remove. Can be omitted if the device has exactly 1 chip
203     pub chip_name: Option<String>,
204 }
205 
206 #[derive(Debug, Args, PartialEq, Default)]
207 pub struct BeaconBleAdvertiseData {
208     /// Whether the device name should be included in the advertise packet
209     #[arg(long, required = false)]
210     pub include_device_name: bool,
211     /// Whether the transmission power level should be included in the advertise packet
212     #[arg(long, required = false)]
213     pub include_tx_power_level: bool,
214     /// Manufacturer-specific data given as bytes in hexadecimal
215     #[arg(long)]
216     pub manufacturer_data: Option<ParsableBytes>,
217 }
218 
219 #[derive(Debug, Clone, PartialEq)]
220 pub struct ParsableBytes(pub Vec<u8>);
221 
222 impl ParsableBytes {
unwrap(self) -> Vec<u8>223     fn unwrap(self) -> Vec<u8> {
224         self.0
225     }
226 }
227 
228 impl FromStr for ParsableBytes {
229     type Err = FromHexError;
from_str(s: &str) -> Result<Self, Self::Err>230     fn from_str(s: &str) -> Result<Self, Self::Err> {
231         hex_to_bytes(s.strip_prefix("0x").unwrap_or(s)).map(ParsableBytes)
232     }
233 }
234 
235 #[derive(Debug, Args, PartialEq, Default)]
236 pub struct BeaconBleScanResponseData {
237     /// Whether the device name should be included in the scan response packet
238     #[arg(long, required = false)]
239     pub scan_response_include_device_name: bool,
240     /// Whether the transmission power level should be included in the scan response packet
241     #[arg(long, required = false)]
242     pub scan_response_include_tx_power_level: bool,
243     /// Manufacturer-specific data to include in the scan response packet given as bytes in hexadecimal
244     #[arg(long, value_name = "MANUFACTURER_DATA")]
245     pub scan_response_manufacturer_data: Option<ParsableBytes>,
246 }
247 
248 #[derive(Debug, Args, PartialEq, Default)]
249 pub struct BeaconBleSettings {
250     /// Set advertise mode to control the advertising latency
251     #[arg(long, value_parser = IntervalParser)]
252     pub advertise_mode: Option<Interval>,
253     /// Set advertise TX power level to control the beacon's transmission power
254     #[arg(long, value_parser = TxPowerParser, allow_hyphen_values = true)]
255     pub tx_power_level: Option<TxPower>,
256     /// Set whether the beacon will respond to scan requests
257     #[arg(long)]
258     pub scannable: bool,
259     /// Limit advertising to an amount of time given in milliseconds
260     #[arg(long, value_name = "MS")]
261     pub timeout: Option<u64>,
262 }
263 
264 #[derive(Clone, Debug, PartialEq)]
265 pub enum Interval {
266     Mode(AdvertiseMode),
267     Milliseconds(u64),
268 }
269 
270 #[derive(Clone)]
271 struct IntervalParser;
272 
273 impl TypedValueParser for IntervalParser {
274     type Value = Interval;
275 
parse_ref( &self, cmd: &clap::Command, arg: Option<&clap::Arg>, value: &std::ffi::OsStr, ) -> Result<Self::Value, clap::Error>276     fn parse_ref(
277         &self,
278         cmd: &clap::Command,
279         arg: Option<&clap::Arg>,
280         value: &std::ffi::OsStr,
281     ) -> Result<Self::Value, clap::Error> {
282         let millis_parser = clap::value_parser!(u64);
283         let mode_parser = clap::value_parser!(AdvertiseMode);
284 
285         mode_parser
286             .parse_ref(cmd, arg, value)
287             .map(Self::Value::Mode)
288             .or(millis_parser.parse_ref(cmd, arg, value).map(Self::Value::Milliseconds))
289     }
290 
possible_values(&self) -> Option<Box<dyn Iterator<Item = PossibleValue> + '_>>291     fn possible_values(&self) -> Option<Box<dyn Iterator<Item = PossibleValue> + '_>> {
292         Some(Box::new(
293             AdvertiseMode::value_variants().iter().map(|v| v.to_possible_value().unwrap()).chain(
294                 iter::once(
295                     PossibleValue::new("<MS>").help("An exact advertise interval in milliseconds"),
296                 ),
297             ),
298         ))
299     }
300 }
301 
302 #[derive(Clone, Debug, PartialEq)]
303 pub enum TxPower {
304     Level(TxPowerLevel),
305     Dbm(i8),
306 }
307 
308 #[derive(Clone)]
309 struct TxPowerParser;
310 
311 impl TypedValueParser for TxPowerParser {
312     type Value = TxPower;
313 
parse_ref( &self, cmd: &clap::Command, arg: Option<&clap::Arg>, value: &std::ffi::OsStr, ) -> Result<Self::Value, clap::Error>314     fn parse_ref(
315         &self,
316         cmd: &clap::Command,
317         arg: Option<&clap::Arg>,
318         value: &std::ffi::OsStr,
319     ) -> Result<Self::Value, clap::Error> {
320         let dbm_parser = clap::value_parser!(i8);
321         let level_parser = clap::value_parser!(TxPowerLevel);
322 
323         level_parser
324             .parse_ref(cmd, arg, value)
325             .map(Self::Value::Level)
326             .or(dbm_parser.parse_ref(cmd, arg, value).map(Self::Value::Dbm))
327     }
328 
possible_values(&self) -> Option<Box<dyn Iterator<Item = PossibleValue> + '_>>329     fn possible_values(&self) -> Option<Box<dyn Iterator<Item = PossibleValue> + '_>> {
330         Some(Box::new(
331             TxPowerLevel::value_variants().iter().map(|v| v.to_possible_value().unwrap()).chain(
332                 iter::once(
333                     PossibleValue::new("<DBM>").help("An exact transmit power level in dBm"),
334                 ),
335             ),
336         ))
337     }
338 }
339 
340 #[derive(Debug, Clone, ValueEnum, PartialEq)]
341 pub enum AdvertiseMode {
342     /// Lowest power consumption, preferred advertising mode
343     LowPower,
344     /// Balanced between advertising frequency and power consumption
345     Balanced,
346     /// Highest power consumption
347     LowLatency,
348 }
349 
350 #[derive(Debug, Clone, ValueEnum, PartialEq)]
351 pub enum TxPowerLevel {
352     /// Lowest transmission power level
353     UltraLow,
354     /// Low transmission power level
355     Low,
356     /// Medium transmission power level
357     Medium,
358     /// High transmission power level
359     High,
360 }
361 
362 #[derive(Debug, Subcommand, PartialEq)]
363 pub enum Capture {
364     /// List currently available Captures (packet captures)
365     List(ListCapture),
366     /// Patch a Capture source to turn packet capture on/off
367     Patch(PatchCapture),
368     /// Download the packet capture content
369     Get(GetCapture),
370 }
371 
372 #[derive(Debug, Args, PartialEq, Default)]
373 pub struct ListCapture {
374     /// Optional strings of pattern for captures to list. Possible filter fields include Capture ID, Device Name, and Chip Kind
375     pub patterns: Vec<String>,
376     /// Continuously print Capture information every second
377     #[arg(short, long)]
378     pub continuous: bool,
379 }
380 
381 #[derive(Debug, Args, PartialEq, Default)]
382 pub struct PatchCapture {
383     /// Packet capture state
384     #[arg(value_enum, ignore_case = true)]
385     pub state: OnOffState,
386     /// Optional strings of pattern for captures to patch. Possible filter fields include Capture ID, Device Name, and Chip Kind
387     pub patterns: Vec<String>,
388 }
389 
390 #[derive(Debug, Args, PartialEq, Default)]
391 pub struct GetCapture {
392     /// Optional strings of pattern for captures to get. Possible filter fields include Capture ID, Device Name, and Chip Kind
393     pub patterns: Vec<String>,
394     /// Directory to store downloaded capture file(s)
395     #[arg(short = 'o', long)]
396     pub location: Option<String>,
397     #[arg(skip)]
398     pub filenames: Vec<String>,
399     #[arg(skip)]
400     pub current_file: String,
401 }
402 
403 impl From<&BeaconBleSettings> for AdvertiseSettingsProto {
from(value: &BeaconBleSettings) -> Self404     fn from(value: &BeaconBleSettings) -> Self {
405         AdvertiseSettingsProto {
406             interval: value.advertise_mode.as_ref().map(IntervalProto::from),
407             tx_power: value.tx_power_level.as_ref().map(TxPowerProto::from),
408             scannable: value.scannable,
409             timeout: value.timeout.unwrap_or_default(),
410             ..Default::default()
411         }
412     }
413 }
414 
415 impl From<&Interval> for IntervalProto {
from(value: &Interval) -> Self416     fn from(value: &Interval) -> Self {
417         match value {
418             Interval::Mode(mode) => IntervalProto::AdvertiseMode(
419                 match mode {
420                     AdvertiseMode::LowPower => AdvertiseModeProto::LOW_POWER,
421                     AdvertiseMode::Balanced => AdvertiseModeProto::BALANCED,
422                     AdvertiseMode::LowLatency => AdvertiseModeProto::LOW_LATENCY,
423                 }
424                 .into(),
425             ),
426             Interval::Milliseconds(ms) => IntervalProto::Milliseconds(*ms),
427         }
428     }
429 }
430 
431 impl From<&TxPower> for TxPowerProto {
from(value: &TxPower) -> Self432     fn from(value: &TxPower) -> Self {
433         match value {
434             TxPower::Level(level) => TxPowerProto::TxPowerLevel(
435                 match level {
436                     TxPowerLevel::UltraLow => AdvertiseTxPowerProto::ULTRA_LOW,
437                     TxPowerLevel::Low => AdvertiseTxPowerProto::LOW,
438                     TxPowerLevel::Medium => AdvertiseTxPowerProto::MEDIUM,
439                     TxPowerLevel::High => AdvertiseTxPowerProto::HIGH,
440                 }
441                 .into(),
442             ),
443             TxPower::Dbm(dbm) => TxPowerProto::Dbm((*dbm).into()),
444         }
445     }
446 }
447 
448 impl From<&BeaconBleAdvertiseData> for AdvertiseDataProto {
from(value: &BeaconBleAdvertiseData) -> Self449     fn from(value: &BeaconBleAdvertiseData) -> Self {
450         AdvertiseDataProto {
451             include_device_name: value.include_device_name,
452             include_tx_power_level: value.include_tx_power_level,
453             manufacturer_data: value
454                 .manufacturer_data
455                 .clone()
456                 .map(ParsableBytes::unwrap)
457                 .unwrap_or_default(),
458             ..Default::default()
459         }
460     }
461 }
462 
463 impl From<&BeaconBleScanResponseData> for AdvertiseDataProto {
from(value: &BeaconBleScanResponseData) -> Self464     fn from(value: &BeaconBleScanResponseData) -> Self {
465         AdvertiseDataProto {
466             include_device_name: value.scan_response_include_device_name,
467             include_tx_power_level: value.scan_response_include_tx_power_level,
468             manufacturer_data: value
469                 .scan_response_manufacturer_data
470                 .clone()
471                 .map(ParsableBytes::unwrap)
472                 .unwrap_or_default(),
473             ..Default::default()
474         }
475     }
476 }
477 
478 #[cfg(test)]
479 mod tests {
480     use super::*;
481 
482     #[test]
test_hex_parser_succeeds()483     fn test_hex_parser_succeeds() {
484         let hex = ParsableBytes::from_str("beef1234");
485         assert!(hex.is_ok(), "{}", hex.unwrap_err());
486         let hex = hex.unwrap().unwrap();
487 
488         assert_eq!(vec![0xbeu8, 0xef, 0x12, 0x34], hex);
489     }
490 
491     #[test]
test_hex_parser_prefix_succeeds()492     fn test_hex_parser_prefix_succeeds() {
493         let hex = ParsableBytes::from_str("0xabcd");
494         assert!(hex.is_ok(), "{}", hex.unwrap_err());
495         let hex = hex.unwrap().unwrap();
496 
497         assert_eq!(vec![0xabu8, 0xcd], hex);
498     }
499 
500     #[test]
test_hex_parser_empty_str_succeeds()501     fn test_hex_parser_empty_str_succeeds() {
502         let hex = ParsableBytes::from_str("");
503         assert!(hex.is_ok(), "{}", hex.unwrap_err());
504         let hex = hex.unwrap().unwrap();
505 
506         assert_eq!(Vec::<u8>::new(), hex);
507     }
508 
509     #[test]
test_hex_parser_bad_digit_fails()510     fn test_hex_parser_bad_digit_fails() {
511         assert!(ParsableBytes::from_str("0xabcdefg").is_err());
512     }
513 }
514