1 /*
2  * Copyright (C) 2023 Huawei Device Co., Ltd.
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  *     http://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 super::translate;
16 
17 use hdc::config::{self, HdcCommand};
18 
19 use hdc::utils;
20 use std::collections::HashMap;
21 use std::io::{self, Error, ErrorKind};
22 use std::str::FromStr;
23 
24 #[derive(Default, Debug, Clone)]
25 pub struct Parsed {
26     pub options: Vec<String>,
27     pub command: Option<HdcCommand>,
28     pub parameters: Vec<String>,
29 }
30 
31 lazy_static! {
32     static ref CMD_MAP: HashMap<&'static str, HdcCommand> = {
33         let mut map = HashMap::new();
34 
35         map.insert("version", HdcCommand::ClientVersion);
36         map.insert("help", HdcCommand::KernelHelp);
37         map.insert("discover", HdcCommand::KernelTargetDiscover);
38         map.insert("start", HdcCommand::KernelServerStart);
39         map.insert("kill", HdcCommand::KernelServerKill);
40         map.insert("keygen", HdcCommand::ClientKeyGenerate);
41         map.insert("list targets", HdcCommand::KernelTargetList);
42         map.insert("checkserver", HdcCommand::KernelCheckServer);
43         map.insert("checkdevice", HdcCommand::KernelCheckDevice);
44         map.insert("wait", HdcCommand::KernelWaitFor);
45         map.insert("tconn", HdcCommand::KernelTargetConnect);
46         map.insert("any", HdcCommand::KernelTargetAny);
47         map.insert("shell", HdcCommand::UnityExecute);
48         map.insert("target boot", HdcCommand::UnityReboot);
49         map.insert("target mount", HdcCommand::UnityRemount);
50         map.insert("smode", HdcCommand::UnityRootrun);
51         map.insert("tmode", HdcCommand::UnityRunmode);
52         map.insert("bugreport", HdcCommand::UnityBugreportInit);
53         map.insert("hilog", HdcCommand::UnityHilog);
54         map.insert("file send", HdcCommand::FileInit);
55         map.insert("file recv", HdcCommand::FileRecvInit);
56         map.insert("fport", HdcCommand::ForwardInit);
57         map.insert("rport", HdcCommand::ForwardRportInit);
58         map.insert("rport ls", HdcCommand::ForwardRportList);
59         map.insert("fport ls", HdcCommand::ForwardList);
60         map.insert("fport rm", HdcCommand::ForwardRemove);
61         map.insert("rport rm", HdcCommand::ForwardRportRemove);
62         map.insert("install", HdcCommand::AppInit);
63         map.insert("uninstall", HdcCommand::AppUninstall);
64         map.insert("sideload", HdcCommand::AppSideload);
65         map.insert("jpid", HdcCommand::JdwpList);
66         map.insert("track-jpid", HdcCommand::JdwpTrack);
67         map.insert("alive", HdcCommand::KernelEnableKeepalive);
68         map.insert("update", HdcCommand::FlashdUpdateInit);
69         map.insert("flash", HdcCommand::FlashdFlashInit);
70         map.insert("erase", HdcCommand::FlashdErase);
71         map.insert("format", HdcCommand::FlashdFormat);
72 
73         map
74     };
75 }
76 
77 const MAX_CMD_LEN: usize = 3;
78 const MAX_CONNECTKEY_SIZE: u16 = 32;
79 const COMBINED_COMMAND_LEN: usize = 2;
80 
81 // TODO: trial tree
split_opt_and_cmdnull82 pub fn split_opt_and_cmd(input: Vec<String>) -> Parsed {
83     let mut cmd_opt: Option<HdcCommand> = None;
84     let mut cmd_index = input.len();
85     for st in 0..input.len() {
86         for len in 1..MAX_CMD_LEN {
87             if st + len > input.len() {
88                 break;
89             }
90             let cmd = input[st..st + len].join(" ");
91             if let Some(command) = CMD_MAP.get(cmd.as_str()) {
92                 // if first command parsed is "fport", but next command parsed
93                 // is not "fport ls" or "fport rm", discard the new command.
94                 if cmd_opt.is_some()
95                     && (cmd_opt.unwrap() == HdcCommand::ForwardInit
96                         || cmd_opt.unwrap() == HdcCommand::ForwardRportInit)
97                     && (*command != HdcCommand::ForwardRemove
98                         && *command != HdcCommand::ForwardList
99                         && *command != HdcCommand::ForwardRportList
100                         && *command != HdcCommand::ForwardRportRemove)
101                 {
102                     break;
103                 }
104                 cmd_index = st;
105                 cmd_opt = Some(command.to_owned());
106                 if *command == HdcCommand::ForwardInit || *command == HdcCommand::ForwardRportInit {
107                     continue;
108                 } else {
109                     break;
110                 }
111             }
112         }
113         if cmd_opt.is_some()
114             && cmd_opt.unwrap() != HdcCommand::ForwardInit
115             && cmd_opt.unwrap() != HdcCommand::ForwardRportInit
116         {
117             break;
118         }
119     }
120     Parsed {
121         options: input[..cmd_index].to_vec(),
122         command: cmd_opt,
123         parameters: input[cmd_index..].to_vec(),
124     }
125 }
126 
parse_commandnull127 pub fn parse_command(args: std::env::Args) -> io::Result<ParsedCommand> {
128     let input = args.collect::<Vec<_>>()[1..].to_vec();
129     let parsed = split_opt_and_cmd(input);
130     match extract_global_params(parsed.options) {
131         Ok(parsed_cmd) => Ok(ParsedCommand {
132             command: parsed.command,
133             parameters: parsed.parameters,
134             ..parsed_cmd
135         }),
136         Err(e) => Err(e),
137     }
138 }
139 
exchange_parsed_for_daemonnull140 pub fn exchange_parsed_for_daemon(mut parsed: Parsed) -> Parsed  {
141     if let Some(HdcCommand::UnityReboot) = parsed.command {
142         if parsed.parameters.len() > COMBINED_COMMAND_LEN {
143             let valid_boot_cmd = ["-bootloader", "-recovery", "-flashd"];
144             for str in valid_boot_cmd {
145                 if str == parsed.parameters[COMBINED_COMMAND_LEN].as_str() {
146                     parsed.parameters.clear();
147                     parsed.parameters.push(str.to_string()[1..].to_string());
148                     return parsed;
149                 }
150             }
151         }
152         parsed.parameters.clear();
153         hdc::info!("parsed parameter is {:?}", parsed.parameters);
154     }
155     parsed
156 }
157 
158 #[derive(Default, Debug, PartialEq)]
159 pub struct ParsedCommand {
160     pub run_in_server: bool,
161     pub launch_server: bool,
162     pub spawned_server: bool,
163     pub connect_key: String,
164     pub log_level: usize,
165     pub server_addr: String,
166     pub command: Option<HdcCommand>,
167     pub parameters: Vec<String>,
168 }
169 
extract_global_paramsnull170 pub fn extract_global_params(opts: Vec<String>) -> io::Result<ParsedCommand> {
171     let mut parsed_command = ParsedCommand {
172         launch_server: true,
173         log_level: 3,
174         server_addr: format!("127.0.0.1:{}", config::SERVER_DEFAULT_PORT),
175         ..Default::default()
176     };
177     let len = opts.len();
178     for i in 0..len {
179         let opt = opts[i].as_str();
180         let arg = if opt.len() > 2 {
181             &opt[2..]
182         } else if i < len - 1 {
183             opts[i + 1].as_str()
184         } else {
185             ""
186         };
187         if opt.starts_with("-h") {
188             if arg == "verbose" {
189                 return Err(utils::error_other(translate::verbose()));
190             } else {
191                 return Err(utils::error_other(translate::usage()));
192             }
193         } else if opt.starts_with("-v") {
194             return Err(utils::error_other(config::get_version()));
195         } else if opt.starts_with("-l") {
196             if let Ok(level) = arg.parse::<usize>() {
197                 if level < config::LOG_LEVEL_ORDER.len() {
198                     parsed_command.log_level = level;
199                 } else {
200                     return Err(utils::error_other(format!(
201                         "-l content loglevel incorrect\n\n{}",
202                         translate::usage()
203                     )));
204                 }
205             } else {
206                 return Err(utils::error_other(format!(
207                     "-l content loglevel incorrect\n\n{}",
208                     translate::usage()
209                 )));
210             }
211         } else if opt.starts_with("-m") {
212             parsed_command.run_in_server = true;
213         } else if opt.starts_with("-p") {
214             parsed_command.launch_server = false;
215         } else if opt.starts_with("-t") {
216             parsed_command.connect_key = arg.to_string();
217             if arg.len() > MAX_CONNECTKEY_SIZE.into() {
218                 return Err(utils::error_other(format!(
219                     "Sizeo of of parament '-t' {} is too long\n",
220                     arg.len()
221                 )));
222             }
223         } else if opt.starts_with("-s") {
224             match parse_server_listen_string(arg.to_string()) {
225                 Ok(server_addr) => parsed_command.server_addr = server_addr,
226                 Err(e) => {
227                     return Err(utils::error_other(format!(
228                         "{}\n\n{}",
229                         e,
230                         translate::usage()
231                     )));
232                 }
233             }
234         } else if opt.starts_with("-b") {
235             // server spawned by client, no stdout
236             parsed_command.spawned_server = true;
237         }
238     }
239     Ok(parsed_command)
240 }
241 
check_portnull242 fn check_port(port_str: String) -> io::Result<u16> {
243     if let Ok(port) = port_str.parse::<u16>() {
244         return Ok(port);
245     }
246     Err(Error::new(ErrorKind::Other, "-s content port incorrect"))
247 }
248 
parse_server_listen_stringnull249 fn parse_server_listen_string(arg: String) -> io::Result<String> {
250     let segments: Vec<&str> = arg.split(':').collect();
251     let port_str = match segments.last() {
252         Some(str) => str.to_string(),
253         None => {
254             return Err(Error::new(ErrorKind::Other, "-s content ip incorrect"));
255         }
256     };
257     let port_len = port_str.len();
258     let port = check_port(port_str)?;
259 
260     if segments.len() == 1 {
261         return Ok(format!(
262             // "{}{}:{}",
263             // config::IPV4_MAPPING_PREFIX,
264             "{}:{}",
265             config::LOCAL_HOST,
266             port
267         ));
268     }
269 
270     let ip_str = &arg[..arg.len() - port_len - 1];
271     match std::net::IpAddr::from_str(ip_str) {
272         Ok(ip_addr) => {
273             if ip_addr.is_ipv4() || ip_addr.is_ipv6() {
274                 Ok(arg)
275             } else {
276                 Err(Error::new(ErrorKind::Other, "-s content ip incorrect"))
277             }
278         }
279         _ => Err(Error::new(ErrorKind::Other, "-s content ip incorrect")),
280     }
281 }
282