Skip to main content

nitro_cli/common/
commands_parser.rs

1// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0
3#![deny(missing_docs)]
4#![deny(warnings)]
5
6use clap::ArgMatches;
7use libc::VMADDR_CID_HOST;
8#[cfg(test)]
9use libc::VMADDR_CID_LOCAL;
10use serde::{Deserialize, Serialize};
11use std::fs::File;
12use std::str::FromStr;
13
14use crate::common::{NitroCliErrorEnum, NitroCliFailure, NitroCliResult, VMADDR_CID_PARENT};
15use crate::get_id_by_name;
16use crate::new_nitro_cli_failure;
17use crate::utils::PcrType;
18
19/// The arguments used by the `run-enclave` command.
20#[derive(Debug, Clone, Serialize, Deserialize)]
21pub struct RunEnclavesArgs {
22    /// The path to the enclave image file.
23    pub eif_path: String,
24    /// The optional enclave CID
25    pub enclave_cid: Option<u64>,
26    /// The amount of memory that will be given to the enclave.
27    pub memory_mib: u64,
28    /// An optional list of CPU IDs that will be given to the enclave.
29    pub cpu_ids: Option<Vec<u32>>,
30    /// A flag indicating if the enclave will be started in debug mode.
31    #[serde(default)]
32    pub debug_mode: bool,
33    /// Attach to the console immediately if using debug mode.
34    #[serde(default)]
35    pub attach_console: bool,
36    /// The number of CPUs that the enclave will receive.
37    pub cpu_count: Option<u32>,
38    /// Enclave name set by the user.
39    pub enclave_name: Option<String>,
40}
41
42impl RunEnclavesArgs {
43    /// Construct a new `RunEnclavesArgs` instance from the given command-line arguments.
44    pub fn new_with(args: &ArgMatches) -> NitroCliResult<Self> {
45        if let Some(config_file) = args.get_one::<String>("config") {
46            let file = File::open(config_file).map_err(|err| {
47                new_nitro_cli_failure!(
48                    &format!("Failed to open config file: {err:?}"),
49                    NitroCliErrorEnum::FileOperationFailure
50                )
51                .add_info(vec![config_file, "Open"])
52            })?;
53
54            let mut json: RunEnclavesArgs = serde_json::from_reader(file).map_err(|err| {
55                new_nitro_cli_failure!(
56                    &format!("Invalid JSON format for config file: {err:?}"),
57                    NitroCliErrorEnum::SerdeError
58                )
59            })?;
60            if json.cpu_count.is_none() && json.cpu_ids.is_none() {
61                return Err(new_nitro_cli_failure!(
62                    "Missing both `cpu-count` and `cpu-ids`",
63                    NitroCliErrorEnum::MissingArgument
64                ));
65            }
66            if json.cpu_count.is_some() && json.cpu_ids.is_some() {
67                return Err(new_nitro_cli_failure!(
68                    "`cpu-count` and `cpu-ids` cannot be used together",
69                    NitroCliErrorEnum::ConflictingArgument
70                ));
71            }
72
73            // attach_console implies debug_mode
74            json.debug_mode = json.debug_mode || json.attach_console;
75
76            Ok(json)
77        } else {
78            Ok(RunEnclavesArgs {
79                cpu_count: parse_cpu_count(args)
80                    .map_err(|err| err.add_subaction("Parse CPU count".to_string()))?,
81                eif_path: parse_eif_path(args)
82                    .map_err(|err| err.add_subaction("Parse EIF path".to_string()))?,
83                enclave_cid: parse_enclave_cid(args)
84                    .map_err(|err| err.add_subaction("Parse enclave CID".to_string()))?,
85                memory_mib: parse_memory(args)
86                    .map_err(|err| err.add_subaction("Parse memory".to_string()))?,
87                cpu_ids: parse_cpu_ids(args)
88                    .map_err(|err| err.add_subaction("Parse CPU IDs".to_string()))?,
89                debug_mode: debug_mode(args),
90                attach_console: attach_console(args),
91                enclave_name: parse_enclave_name(args)
92                    .map_err(|err| err.add_subaction("Parse enclave name".to_string()))?,
93            })
94        }
95    }
96}
97
98/// The arguments used by the `build-enclave` command.
99#[derive(Debug, Clone)]
100pub struct BuildEnclavesArgs {
101    /// The URI to the Docker image.
102    pub docker_uri: String,
103    /// The directory containing the Docker image.
104    pub docker_dir: Option<String>,
105    /// The path where the enclave image file will be written to.
106    pub output: String,
107    /// The path to the signing certificate for signed enclaves.
108    pub signing_certificate: Option<String>,
109    /// KMS key ARN or path to the private key for signed enclaves.
110    pub private_key: Option<String>,
111    /// The name of the enclave image.
112    pub img_name: Option<String>,
113    /// The version of the enclave image.
114    pub img_version: Option<String>,
115    /// The path to custom metadata JSON file
116    pub metadata: Option<String>,
117}
118
119impl BuildEnclavesArgs {
120    /// Construct a new `BuildEnclavesArgs` instance from the given command-line arguments.
121    pub fn new_with(args: &ArgMatches) -> NitroCliResult<Self> {
122        Ok(BuildEnclavesArgs {
123            docker_uri: parse_docker_tag(args).ok_or_else(|| {
124                new_nitro_cli_failure!(
125                    "`docker-uri` argument not found",
126                    NitroCliErrorEnum::MissingArgument
127                )
128                .add_info(vec!["docker-uri"])
129            })?,
130            docker_dir: parse_docker_dir(args),
131            output: parse_output(args).ok_or_else(|| {
132                new_nitro_cli_failure!(
133                    "`output` argument not found",
134                    NitroCliErrorEnum::MissingArgument
135                )
136                .add_info(vec!["output"])
137            })?,
138            signing_certificate: parse_signing_certificate(args),
139            private_key: parse_private_key(args),
140            img_name: parse_image_name(args),
141            img_version: parse_image_version(args),
142            metadata: parse_metadata(args),
143        })
144    }
145}
146
147/// The arguments used by the `terminate-enclave` command.
148#[derive(Debug, Clone, Serialize, Deserialize)]
149pub struct TerminateEnclavesArgs {
150    /// The ID of the enclave that is to be terminated.
151    pub enclave_id: String,
152}
153
154impl TerminateEnclavesArgs {
155    /// Construct a new `TerminateEnclavesArgs` instance from the given command-line arguments.
156    pub fn new_with(args: &ArgMatches) -> NitroCliResult<Self> {
157        // If a name is given, find the corresponding EnclaveID
158        match parse_enclave_name(args)
159            .map_err(|e| e.add_subaction("Parse Enclave Name".to_string()))?
160        {
161            Some(name) => Ok(TerminateEnclavesArgs {
162                enclave_id: get_id_by_name(name)
163                    .map_err(|e| e.add_subaction("Get ID by Name".to_string()))?,
164            }),
165            None => Ok(TerminateEnclavesArgs {
166                enclave_id: parse_enclave_id(args)
167                    .map_err(|e| e.add_subaction("Parse enclave ID".to_string()))?,
168            }),
169        }
170    }
171}
172
173/// The arguments used by the `console` command.
174#[derive(Debug, Clone, Serialize, Deserialize)]
175pub struct ConsoleArgs {
176    /// The ID of the enclave whose console is to be shown.
177    pub enclave_id: String,
178    /// The time in seconds after the console disconnects from the enclave.
179    pub disconnect_timeout_sec: Option<u64>,
180}
181
182impl ConsoleArgs {
183    /// Construct a new `ConsoleArgs` instance from the given command-line arguments.
184    pub fn new_with(args: &ArgMatches) -> NitroCliResult<Self> {
185        // If a name is given, find the corresponding EnclaveID
186        let enclave_id = match parse_enclave_name(args)
187            .map_err(|e| e.add_subaction("Parse Enclave Name".to_string()))?
188        {
189            Some(name) => {
190                get_id_by_name(name).map_err(|e| e.add_subaction("Get ID by Name".to_string()))?
191            }
192            None => parse_enclave_id(args)
193                .map_err(|e| e.add_subaction("Parse enclave ID".to_string()))?,
194        };
195
196        Ok(ConsoleArgs {
197            enclave_id,
198            disconnect_timeout_sec: parse_disconnect_timeout(args)
199                .map_err(|e| e.add_subaction("Parse disconnect timeout".to_string()))?,
200        })
201    }
202}
203
204/// Empty set of arguments.
205#[derive(Serialize, Deserialize)]
206pub struct EmptyArgs {}
207
208/// The arguments used by `describe-enclaves` command.
209#[derive(Debug, Clone, Serialize, Deserialize)]
210pub struct DescribeEnclavesArgs {
211    /// True if metadata is requested.
212    pub metadata: bool,
213}
214
215impl DescribeEnclavesArgs {
216    /// Construct a new `DescribeEnclavesArgs` instance from the given command-line arguments.
217    pub fn new_with(args: &ArgMatches) -> Self {
218        DescribeEnclavesArgs {
219            metadata: args.get_flag("metadata"),
220        }
221    }
222}
223
224/// The arguments used by the `explain` command.
225#[derive(Debug, Clone)]
226pub struct ExplainArgs {
227    /// The error code of the error to explain.
228    pub error_code_str: String,
229}
230
231impl ExplainArgs {
232    /// Construct a new `ExplainArgs` instance from the given command-line arguments.
233    pub fn new_with(args: &ArgMatches) -> NitroCliResult<Self> {
234        Ok(ExplainArgs {
235            error_code_str: parse_error_code_str(args)
236                .map_err(|e| e.add_subaction("Parse error code".to_string()))?,
237        })
238    }
239}
240
241/// The arguments used by `pcr` command
242pub struct PcrArgs {
243    /// Path to the file needed for hashing
244    pub path: String,
245    /// The type of file we need to hash
246    pub pcr_type: PcrType,
247}
248
249impl PcrArgs {
250    /// Construct a new `PcrArgs` instance from the given command-line arguments.
251    pub fn new_with(args: &ArgMatches) -> NitroCliResult<Self> {
252        let (val_name, pcr_type) = match args.contains_id("signing-certificate") {
253            true => ("signing-certificate", PcrType::SigningCertificate),
254            false => ("input", PcrType::DefaultType),
255        };
256        let path = parse_file_path(args, val_name)
257            .map_err(|e| e.add_subaction("Parse PCR file".to_string()))?;
258        Ok(Self { path, pcr_type })
259    }
260}
261
262/// The arguments used by `sign-eif` command
263#[derive(Debug, Clone)]
264pub struct SignEifArgs {
265    /// Path to the EIF file needed for signing
266    pub eif_path: String,
267    /// The path to the signing certificate for signed enclaves.
268    pub signing_certificate: Option<String>,
269    /// ARN of the KMS key or path to the local private key for signed enclaves.
270    pub private_key: Option<String>,
271}
272
273impl SignEifArgs {
274    /// Construct a new `SignEifArgs` instance from the given command-line arguments.
275    pub fn new_with(args: &ArgMatches) -> NitroCliResult<Self> {
276        let signing_certificate = parse_signing_certificate(args);
277        let private_key = parse_private_key(args);
278
279        Ok(SignEifArgs {
280            eif_path: parse_eif_path(args)
281                .map_err(|e| e.add_subaction("Parse EIF path".to_string()))?,
282            signing_certificate,
283            private_key,
284        })
285    }
286}
287
288/// Parse file path to hash from the command-line arguments.
289fn parse_file_path(args: &ArgMatches, val_name: &str) -> NitroCliResult<String> {
290    let path = args.get_one::<String>(val_name).ok_or_else(|| {
291        new_nitro_cli_failure!(
292            "`input` or `signing-certificate` argument not found",
293            NitroCliErrorEnum::MissingArgument
294        )
295    })?;
296    Ok(path.into())
297}
298
299#[derive(Debug)]
300enum MemoryUnit {
301    Mebibytes,
302    Gibibytes,
303    Tebibytes,
304}
305
306#[derive(Debug)]
307struct UnknownMemoryUnitErr;
308
309impl MemoryUnit {
310    fn to_mebibytes(&self) -> u64 {
311        match self {
312            MemoryUnit::Mebibytes => 1,
313            MemoryUnit::Gibibytes => 1024,
314            MemoryUnit::Tebibytes => 1024 * 1024,
315        }
316    }
317}
318
319impl FromStr for MemoryUnit {
320    type Err = UnknownMemoryUnitErr;
321
322    fn from_str(s: &str) -> Result<Self, Self::Err> {
323        match s {
324            "M" | "m" | "" => Ok(MemoryUnit::Mebibytes),
325            "G" | "g" => Ok(MemoryUnit::Gibibytes),
326            "T" | "t" => Ok(MemoryUnit::Tebibytes),
327            _ => Err(UnknownMemoryUnitErr),
328        }
329    }
330}
331
332/// Parse the requested amount of enclave memory from the command-line arguments.
333/// It can be just a number like 123, or it can end in a size indicator like 100M or 10G.
334/// If the size indicator is missing, it defaults to M.
335/// If the size indicator is not M, G or T, it returns an error.
336///
337/// # Arguments
338/// * `args` - The command-line arguments.
339pub fn parse_memory(args: &ArgMatches) -> NitroCliResult<u64> {
340    let memory = args.get_one::<String>("memory").ok_or_else(|| {
341        new_nitro_cli_failure!(
342            "`memory` argument not found",
343            NitroCliErrorEnum::MissingArgument
344        )
345    })?;
346
347    let (num_str, size_str) = match memory.find(|c: char| !c.is_numeric()) {
348        Some(index) => memory.split_at(index),
349        None => (memory.as_str(), ""),
350    };
351    let num = num_str.parse::<u64>().map_err(|_| {
352        new_nitro_cli_failure!(
353            "`memory` argument does not contain a number",
354            NitroCliErrorEnum::InvalidArgument
355        )
356        .add_info(vec!["memory", memory])
357    })?;
358
359    let unit = size_str.parse::<MemoryUnit>().map_err(|_| {
360        new_nitro_cli_failure!(
361            "`memory` argument does not contain a valid size indicator",
362            NitroCliErrorEnum::InvalidArgument
363        )
364        .add_info(vec!["memory", memory])
365    })?;
366    Ok(num * unit.to_mebibytes())
367}
368
369/// Parse the Docker tag from the command-line arguments.
370fn parse_docker_tag(args: &ArgMatches) -> Option<String> {
371    args.get_one::<String>("docker-uri").map(String::from)
372}
373
374/// Parse the Docker directory from the command-line arguments.
375fn parse_docker_dir(args: &ArgMatches) -> Option<String> {
376    args.get_one::<String>("docker-dir").map(String::from)
377}
378
379/// Parse the enclave's required CID from the command-line arguments.
380fn parse_enclave_cid(args: &ArgMatches) -> NitroCliResult<Option<u64>> {
381    let enclave_cid = if let Some(enclave_cid) = args.get_one::<String>("enclave-cid") {
382        let enclave_cid: u64 = enclave_cid.parse().map_err(|_| {
383            new_nitro_cli_failure!(
384                "`enclave-cid` is not a number",
385                NitroCliErrorEnum::InvalidArgument
386            )
387            .add_info(vec!["enclave-cid", enclave_cid])
388        })?;
389
390        // Do not use well-known CID values - 0, 1, 2 - as the enclave CID.
391        // VMADDR_CID_ANY = -1U
392        // VMADDR_CID_HYPERVISOR = 0
393        // VMADDR_CID_LOCAL = 1
394        // VMADDR_CID_HOST = 2
395        // Note: 0 is used as a placeholder to auto-generate a CID.
396        // <http://man7.org/linux/man-pages/man7/vsock.7.html>
397        if enclave_cid == 0 {
398            eprintln!("The enclave CID will be auto-generated as the provided CID is 0");
399        }
400
401        if enclave_cid > 0 && enclave_cid <= VMADDR_CID_HOST as u64 {
402            return Err(new_nitro_cli_failure!(
403                &format!("CID {enclave_cid} is a well-known CID, not to be used for enclaves"),
404                NitroCliErrorEnum::InvalidArgument
405            ));
406        }
407
408        if enclave_cid == u32::MAX as u64 {
409            return Err(new_nitro_cli_failure!(
410                &format!("CID {enclave_cid} is a well-known CID, not to be used for enclaves"),
411                NitroCliErrorEnum::InvalidArgument
412            ));
413        }
414
415        // Do not use the CID of the parent VM as the enclave CID.
416        if enclave_cid == VMADDR_CID_PARENT as u64 {
417            return Err(new_nitro_cli_failure!(
418                &format!(
419                    "CID {enclave_cid} is the CID of the parent VM, not to be used for enclaves"
420                ),
421                NitroCliErrorEnum::InvalidArgument
422            ));
423        }
424
425        // 64-bit CIDs are not yet supported for the vsock device.
426        if enclave_cid > u32::MAX as u64 {
427            return Err(new_nitro_cli_failure!(
428                &format!(
429                    "CID {enclave_cid} is higher than the maximum supported (u32 max) for a vsock device"
430                ),
431                NitroCliErrorEnum::InvalidArgument
432            ));
433        }
434
435        Some(enclave_cid)
436    } else {
437        None
438    };
439
440    Ok(enclave_cid)
441}
442
443/// Parse the enclave image file path from the command-line arguments.
444fn parse_eif_path(args: &ArgMatches) -> NitroCliResult<String> {
445    let eif_path = args.get_one::<String>("eif-path").ok_or_else(|| {
446        new_nitro_cli_failure!(
447            "`eif-path` argument not found",
448            NitroCliErrorEnum::MissingArgument
449        )
450    })?;
451    Ok(eif_path.into())
452}
453
454/// Parse the enclave's ID from the command-line arguments.
455fn parse_enclave_id(args: &ArgMatches) -> NitroCliResult<String> {
456    let enclave_id = args.get_one::<String>("enclave-id").ok_or_else(|| {
457        new_nitro_cli_failure!(
458            "`enclave-id` argument not found",
459            NitroCliErrorEnum::MissingArgument
460        )
461    })?;
462    Ok(enclave_id.into())
463}
464
465/// Parse the disconnect timeout from the command-line arguments.
466fn parse_disconnect_timeout(args: &ArgMatches) -> NitroCliResult<Option<u64>> {
467    let disconnect_timeout = match args.get_one::<String>("disconnect-timeout") {
468        Some(arg) => Some(arg.parse::<u64>().map_err(|_| {
469            new_nitro_cli_failure!(
470                "`disconnect-timeout` argument can't be parsed as a number",
471                NitroCliErrorEnum::InvalidArgument
472            )
473            .add_info(vec!["disconnect-timeout", arg])
474        })?),
475        None => None,
476    };
477    Ok(disconnect_timeout)
478}
479
480/// Parse the list of requested CPU IDs from the command-line arguments.
481fn parse_cpu_ids(args: &ArgMatches) -> NitroCliResult<Option<Vec<u32>>> {
482    args.get_many::<String>("cpu-ids")
483        .map(|values| {
484            values
485                .map(|id| {
486                    id.parse().map_err(|_| {
487                        new_nitro_cli_failure!(
488                            "`cpu-id` is not a number",
489                            NitroCliErrorEnum::InvalidArgument
490                        )
491                        .add_info(vec!["cpu-id", id])
492                    })
493                })
494                .collect()
495        })
496        .transpose()
497}
498
499/// Parse the requested number of CPUs from the command-line arguments.
500fn parse_cpu_count(args: &ArgMatches) -> NitroCliResult<Option<u32>> {
501    args.get_one::<String>("cpu-count")
502        .map(|count| {
503            count.parse().map_err(|_| {
504                new_nitro_cli_failure!(
505                    "`cpu-count` is not a number",
506                    NitroCliErrorEnum::InvalidArgument
507                )
508                .add_info(vec!["cpu-count", count])
509            })
510        })
511        .transpose()
512}
513
514/// Parse the path of an output file from the command-line arguments.
515fn parse_output(args: &ArgMatches) -> Option<String> {
516    args.get_one::<String>("output-file").map(String::from)
517}
518
519/// Parse the debug-mode flag from the command-line arguments.
520fn debug_mode(args: &ArgMatches) -> bool {
521    args.get_flag("debug-mode") || args.get_flag("attach-console")
522}
523
524/// Parse the attach-console flag from the command-line arguments.
525fn attach_console(args: &ArgMatches) -> bool {
526    args.get_flag("attach-console")
527}
528
529/// Parse the enclave name from the command-line arguments.
530fn parse_enclave_name(args: &ArgMatches) -> NitroCliResult<Option<String>> {
531    Ok(args.get_one::<String>("enclave-name").map(String::from))
532}
533
534fn parse_signing_certificate(args: &ArgMatches) -> Option<String> {
535    args.get_one::<String>("signing-certificate")
536        .map(String::from)
537}
538
539fn parse_private_key(args: &ArgMatches) -> Option<String> {
540    args.get_one::<String>("private-key").map(String::from)
541}
542
543fn parse_image_name(args: &ArgMatches) -> Option<String> {
544    args.get_one::<String>("image_name").map(String::from)
545}
546
547fn parse_image_version(args: &ArgMatches) -> Option<String> {
548    args.get_one::<String>("image_version").map(String::from)
549}
550
551fn parse_metadata(args: &ArgMatches) -> Option<String> {
552    args.get_one::<String>("metadata").map(String::from)
553}
554
555fn parse_error_code_str(args: &ArgMatches) -> NitroCliResult<String> {
556    let error_code_str = args.get_one::<String>("error-code").ok_or_else(|| {
557        new_nitro_cli_failure!(
558            "`error-code` argument not found",
559            NitroCliErrorEnum::MissingArgument
560        )
561    })?;
562    Ok(error_code_str.to_string())
563}
564
565#[cfg(test)]
566mod tests {
567    use super::*;
568
569    use crate::common::construct_error_message;
570    use crate::create_app;
571
572    use clap::{Arg, Command};
573
574    /// Parse the path of the JSON config file
575    fn parse_config_file(args: &ArgMatches) -> NitroCliResult<String> {
576        let config_file = args
577            .get_one::<String>("config")
578            .ok_or(new_nitro_cli_failure!(
579                "`config` argument not found",
580                NitroCliErrorEnum::MissingArgument
581            ))?;
582        Ok(config_file.into())
583    }
584
585    #[test]
586    fn test_parse_memory() {
587        let app = create_app!();
588        let args = vec![
589            "nitro-cli",
590            "run-enclave",
591            "--memory",
592            "256_mb",
593            "--cpu-count",
594            "2",
595            "--eif-path",
596            "non_existing_eif.eif",
597        ];
598
599        let matches = app.try_get_matches_from(args);
600        assert!(matches.is_ok());
601
602        let result = parse_memory(
603            matches
604                .as_ref()
605                .unwrap()
606                .subcommand_matches("run-enclave")
607                .unwrap(),
608        );
609        assert!(result.is_err());
610        if let Err(err_info) = result {
611            let err_str = construct_error_message(&err_info);
612            assert!(err_str.contains("Invalid argument provided"))
613        }
614
615        let app = create_app!();
616        let args = vec![
617            "nitro-cli",
618            "run-enclave",
619            "--memory",
620            "256",
621            "--cpu-count",
622            "2",
623            "--eif-path",
624            "non_existing_eif.eif",
625        ];
626
627        let matches = app.try_get_matches_from(args);
628        assert!(matches.is_ok());
629
630        let result = parse_memory(
631            matches
632                .as_ref()
633                .unwrap()
634                .subcommand_matches("run-enclave")
635                .unwrap(),
636        );
637        assert_eq!(result, Ok(256));
638
639        let app = create_app!();
640        let args = vec![
641            "nitro-cli",
642            "run-enclave",
643            "--memory",
644            "100M",
645            "--cpu-count",
646            "2",
647            "--eif-path",
648            "non_existing_eif.eif",
649        ];
650
651        let matches = app.try_get_matches_from(args);
652        assert!(matches.is_ok());
653
654        let result = parse_memory(
655            matches
656                .as_ref()
657                .unwrap()
658                .subcommand_matches("run-enclave")
659                .unwrap(),
660        );
661        assert_eq!(result, Ok(100));
662
663        let app = create_app!();
664        let args = vec![
665            "nitro-cli",
666            "run-enclave",
667            "--memory",
668            "10G",
669            "--cpu-count",
670            "2",
671            "--eif-path",
672            "non_existing_eif.eif",
673        ];
674
675        let matches = app.try_get_matches_from(args);
676        assert!(matches.is_ok());
677
678        let result = parse_memory(
679            matches
680                .as_ref()
681                .unwrap()
682                .subcommand_matches("run-enclave")
683                .unwrap(),
684        );
685        assert_eq!(result, Ok(10_240));
686
687        let app = create_app!();
688        let args = vec![
689            "nitro-cli",
690            "run-enclave",
691            "--memory",
692            "2T",
693            "--cpu-count",
694            "2",
695            "--eif-path",
696            "non_existing_eif.eif",
697        ];
698
699        let matches = app.try_get_matches_from(args);
700        assert!(matches.is_ok());
701
702        let result = parse_memory(
703            matches
704                .as_ref()
705                .unwrap()
706                .subcommand_matches("run-enclave")
707                .unwrap(),
708        );
709        assert_eq!(result, Ok(2 * 1024 * 1024));
710    }
711
712    #[test]
713    fn test_parse_docker_tag() {
714        let app = create_app!();
715        let args = vec![
716            "nitro-cli",
717            "build-enclave",
718            "--docker-uri",
719            "mytag",
720            "--docker-dir",
721            "/home/user/non_existing_dir",
722            "--output-file",
723            "sample_eif.eif",
724        ];
725        let matches = app.try_get_matches_from(args);
726        assert!(matches.is_ok());
727
728        let result = parse_docker_tag(
729            matches
730                .as_ref()
731                .unwrap()
732                .subcommand_matches("build-enclave")
733                .unwrap(),
734        );
735        assert!(result.is_some());
736        assert_eq!(result.unwrap(), "mytag");
737    }
738
739    #[test]
740    fn test_parse_docker_dir() {
741        let app = create_app!();
742        let args = vec![
743            "nitro-cli",
744            "build-enclave",
745            "--docker-uri",
746            "mytag",
747            "--docker-dir",
748            "/home/user/non_existing_dir",
749            "--output-file",
750            "sample_eif.eif",
751        ];
752        let matches = app.try_get_matches_from(args);
753        assert!(matches.is_ok());
754
755        let result = parse_docker_dir(
756            matches
757                .as_ref()
758                .unwrap()
759                .subcommand_matches("build-enclave")
760                .unwrap(),
761        );
762        assert!(result.is_some());
763        assert_eq!(result.unwrap(), "/home/user/non_existing_dir");
764    }
765
766    #[test]
767    fn test_parse_enclave_cid_correct() {
768        let app = create_app!();
769        let args = vec![
770            "nitro-cli",
771            "run-enclave",
772            "--memory",
773            "256",
774            "--cpu-count",
775            "2",
776            "--eif-path",
777            "non_existing_eif.eif",
778            "--enclave-cid",
779            "10",
780        ];
781        let matches = app.try_get_matches_from(args);
782        assert!(matches.is_ok());
783
784        let result = parse_enclave_cid(
785            matches
786                .as_ref()
787                .unwrap()
788                .subcommand_matches("run-enclave")
789                .unwrap(),
790        );
791        assert!(result.is_ok());
792
793        if let Some(parsed_cid) = result.unwrap() {
794            assert_eq!(parsed_cid, 10);
795        }
796    }
797
798    #[test]
799    fn test_parse_enclave_cid_to_be_autogenerated() {
800        let app = create_app!();
801        let args = vec![
802            "nitro-cli",
803            "run-enclave",
804            "--memory",
805            "256",
806            "--cpu-count",
807            "2",
808            "--eif-path",
809            "non_existing_eif.eif",
810            "--enclave-cid",
811            "0",
812        ];
813        let matches = app.try_get_matches_from(args);
814        assert!(matches.is_ok());
815
816        let result = parse_enclave_cid(
817            matches
818                .as_ref()
819                .unwrap()
820                .subcommand_matches("run-enclave")
821                .unwrap(),
822        );
823        assert!(result.is_ok());
824
825        if let Some(parsed_cid) = result.unwrap() {
826            assert_eq!(parsed_cid, 0);
827        }
828    }
829
830    #[test]
831    fn test_parse_enclave_cid_str() {
832        let app = create_app!();
833        let args = vec![
834            "nitro-cli",
835            "run-enclave",
836            "--memory",
837            "256",
838            "--cpu-count",
839            "2",
840            "--eif-path",
841            "non_existing_eif.eif",
842            "--enclave-cid",
843            "0x1g",
844        ];
845        let matches = app.try_get_matches_from(args);
846        assert!(matches.is_ok());
847
848        let result = parse_enclave_cid(
849            matches
850                .as_ref()
851                .unwrap()
852                .subcommand_matches("run-enclave")
853                .unwrap(),
854        );
855        assert!(result.is_err());
856        if let Err(err_info) = result {
857            let err_str = construct_error_message(&err_info);
858            assert!(err_str.contains("Invalid argument provided"))
859        }
860    }
861
862    #[test]
863    fn test_parse_enclave_cid_well_known_cid_local() {
864        let app = create_app!();
865        let cid_local = VMADDR_CID_LOCAL.to_string();
866        let args = vec![
867            "nitro-cli",
868            "run-enclave",
869            "--memory",
870            "256",
871            "--cpu-count",
872            "2",
873            "--eif-path",
874            "non_existing_eif.eif",
875            "--enclave-cid",
876            &cid_local,
877        ];
878        let matches = app.try_get_matches_from(args);
879        assert!(matches.is_ok());
880
881        let result = parse_enclave_cid(
882            matches
883                .as_ref()
884                .unwrap()
885                .subcommand_matches("run-enclave")
886                .unwrap(),
887        );
888        assert!(result.is_err());
889        if let Err(err_info) = result {
890            let err_str = construct_error_message(&err_info);
891            assert!(err_str.contains("Invalid argument provided"));
892        }
893    }
894
895    #[test]
896    fn test_parse_enclave_cid_well_known_cid_host() {
897        let app = create_app!();
898        let cid_host = VMADDR_CID_HOST.to_string();
899        let args = vec![
900            "nitro-cli",
901            "run-enclave",
902            "--memory",
903            "256",
904            "--cpu-count",
905            "2",
906            "--eif-path",
907            "non_existing_eif.eif",
908            "--enclave-cid",
909            &cid_host,
910        ];
911        let matches = app.try_get_matches_from(args);
912        assert!(matches.is_ok());
913
914        let result = parse_enclave_cid(
915            matches
916                .as_ref()
917                .unwrap()
918                .subcommand_matches("run-enclave")
919                .unwrap(),
920        );
921        assert!(result.is_err());
922        if let Err(err_info) = result {
923            let err_str = construct_error_message(&err_info);
924            assert!(err_str.contains("Invalid argument provided"));
925        }
926    }
927
928    #[test]
929    fn test_parse_enclave_cid_parent_vm() {
930        let app = create_app!();
931        let parent_vm_cid = VMADDR_CID_PARENT.to_string();
932        let args = vec![
933            "nitro-cli",
934            "run-enclave",
935            "--memory",
936            "256",
937            "--cpu-count",
938            "2",
939            "--eif-path",
940            "non_existing_eif.eif",
941            "--enclave-cid",
942            &parent_vm_cid,
943        ];
944        let matches = app.try_get_matches_from(args);
945        assert!(matches.is_ok());
946
947        let result = parse_enclave_cid(
948            matches
949                .as_ref()
950                .unwrap()
951                .subcommand_matches("run-enclave")
952                .unwrap(),
953        );
954        assert!(result.is_err());
955        if let Err(err_info) = result {
956            let err_str = construct_error_message(&err_info);
957            assert!(err_str.contains("Invalid argument provided"));
958        }
959    }
960
961    #[test]
962    fn test_parse_enclave_cid_negative() {
963        let app = create_app!();
964        let args = vec![
965            "nitro-cli",
966            "run-enclave",
967            "--memory",
968            "256",
969            "--cpu-count",
970            "2",
971            "--eif-path",
972            "non_existing_eif.eif",
973            "--enclave-cid",
974            "-18",
975        ];
976        let matches = app.try_get_matches_from(args);
977        // Error (got unexpected value ["-1"])
978        assert!(matches.is_err());
979    }
980
981    #[test]
982    fn test_parse_eif_path() {
983        let app = create_app!();
984        let args = vec![
985            "nitro-cli",
986            "run-enclave",
987            "--memory",
988            "256",
989            "--cpu-count",
990            "2",
991            "--eif-path",
992            "non_existing_eif.eif",
993        ];
994        let matches = app.try_get_matches_from(args);
995        assert!(matches.is_ok());
996
997        let result = parse_eif_path(
998            matches
999                .as_ref()
1000                .unwrap()
1001                .subcommand_matches("run-enclave")
1002                .unwrap(),
1003        );
1004        assert!(result.is_ok());
1005        assert_eq!(result.unwrap(), "non_existing_eif.eif");
1006    }
1007
1008    #[test]
1009    fn test_parse_enclave_id() {
1010        let app = create_app!();
1011        let args = vec![
1012            "nitro-cli",
1013            "terminate-enclave",
1014            "--enclave-id",
1015            "i-0000-enc-1234",
1016        ];
1017        let matches = app.try_get_matches_from(args);
1018        assert!(matches.is_ok());
1019
1020        let result = parse_enclave_id(
1021            matches
1022                .as_ref()
1023                .unwrap()
1024                .subcommand_matches("terminate-enclave")
1025                .unwrap(),
1026        );
1027        assert!(result.is_ok());
1028        assert_eq!(result.unwrap(), "i-0000-enc-1234");
1029    }
1030
1031    #[test]
1032    fn test_parse_cpu_ids_correct() {
1033        let app = create_app!();
1034        let args = vec![
1035            "nitro-cli",
1036            "run-enclave",
1037            "--cpu-ids",
1038            "1",
1039            "3",
1040            "--eif-path",
1041            "non_existing_eif.eif",
1042            "--memory",
1043            "64",
1044        ];
1045        let matches = app.try_get_matches_from(args);
1046        assert!(matches.is_ok());
1047
1048        let result = parse_cpu_ids(
1049            matches
1050                .as_ref()
1051                .unwrap()
1052                .subcommand_matches("run-enclave")
1053                .unwrap(),
1054        );
1055        assert!(result.is_ok());
1056
1057        if let Some(parsed_cpu_ids) = result.unwrap() {
1058            assert_eq!(parsed_cpu_ids.len(), 2);
1059            assert_eq!(parsed_cpu_ids[0], 1);
1060            assert_eq!(parsed_cpu_ids[1], 3);
1061        }
1062    }
1063
1064    #[test]
1065    fn test_parse_cpu_ids_negative() {
1066        let app = create_app!();
1067        let args = vec![
1068            "nitro-cli",
1069            "run-enclave",
1070            "--cpu-ids",
1071            "1",
1072            "-5",
1073            "--eif-path",
1074            "non_existing_eif.eif",
1075            "--memory 64",
1076        ];
1077        let matches = app.try_get_matches_from(args);
1078        // Error (got unexpected value ["-5"])
1079        assert!(matches.is_err());
1080    }
1081
1082    #[test]
1083    fn test_parse_cpu_ids_str() {
1084        let app = create_app!();
1085        let args = vec![
1086            "nitro-cli",
1087            "run-enclave",
1088            "--cpu-ids",
1089            "1",
1090            "three",
1091            "--eif-path",
1092            "non_existing_eif.eif",
1093            "--memory",
1094            "64",
1095        ];
1096        let matches = app.try_get_matches_from(args);
1097        assert!(matches.is_ok());
1098
1099        let result = parse_cpu_ids(
1100            matches
1101                .as_ref()
1102                .unwrap()
1103                .subcommand_matches("run-enclave")
1104                .unwrap(),
1105        );
1106        assert!(result.is_err());
1107        if let Err(err_info) = result {
1108            let err_str = construct_error_message(&err_info);
1109            assert!(err_str.contains("Invalid argument provided"));
1110        }
1111    }
1112
1113    #[test]
1114    fn test_parse_cpu_count_correct() {
1115        let app = create_app!();
1116        let args = vec![
1117            "nitro-cli",
1118            "run-enclave",
1119            "--cpu-count",
1120            "2",
1121            "--eif-path",
1122            "non_existing_eif.eif",
1123            "--memory",
1124            "64",
1125        ];
1126        let matches = app.try_get_matches_from(args);
1127        assert!(matches.is_ok());
1128
1129        let result = parse_cpu_count(
1130            matches
1131                .as_ref()
1132                .unwrap()
1133                .subcommand_matches("run-enclave")
1134                .unwrap(),
1135        );
1136        assert!(result.is_ok());
1137
1138        if let Some(parsed_cpu_count) = result.unwrap() {
1139            assert_eq!(parsed_cpu_count, 2);
1140        }
1141    }
1142
1143    #[test]
1144    fn test_parse_cpu_count_str() {
1145        let app = create_app!();
1146        let args = vec![
1147            "nitro-cli",
1148            "run-enclave",
1149            "--cpu-count",
1150            "2n",
1151            "--eif-path",
1152            "non_existing_eif.eif",
1153            "--memory",
1154            "64",
1155        ];
1156        let matches = app.try_get_matches_from(args);
1157        assert!(matches.is_ok());
1158
1159        let result = parse_cpu_count(
1160            matches
1161                .as_ref()
1162                .unwrap()
1163                .subcommand_matches("run-enclave")
1164                .unwrap(),
1165        );
1166        assert!(result.is_err());
1167        if let Err(err_info) = result {
1168            let err_str = construct_error_message(&err_info);
1169            assert!(err_str.contains("Invalid argument provided"));
1170        }
1171    }
1172
1173    #[test]
1174    fn test_parse_output() {
1175        let app = create_app!();
1176        let args = vec![
1177            "nitro-cli",
1178            "build-enclave",
1179            "--docker-uri",
1180            "mytag",
1181            "--docker-dir",
1182            "/home/user/non_existing_dir",
1183            "--output-file",
1184            "sample_eif.eif",
1185        ];
1186        let matches = app.try_get_matches_from(args);
1187        assert!(matches.is_ok());
1188
1189        let result = parse_output(
1190            matches
1191                .as_ref()
1192                .unwrap()
1193                .subcommand_matches("build-enclave")
1194                .unwrap(),
1195        );
1196        assert!(result.is_some());
1197        assert_eq!(result.unwrap(), "sample_eif.eif");
1198    }
1199
1200    #[test]
1201    fn test_parse_output_not_supplied() {
1202        let app = create_app!();
1203        let args = vec![
1204            "nitro-cli",
1205            "build-enclave",
1206            "--docker-uri",
1207            "mytag",
1208            "--docker-dir",
1209            "/home/user/non_existing_dir",
1210        ];
1211        let matches = app.try_get_matches_from(args);
1212        // Error (the following required argument were not supplied)
1213        assert!(matches.is_err());
1214    }
1215
1216    #[test]
1217    fn test_debug_mode_supplied() {
1218        let app = create_app!();
1219        let args = vec![
1220            "nitro-cli",
1221            "run-enclave",
1222            "--memory",
1223            "64",
1224            "--cpu-count",
1225            "2",
1226            "--eif-path",
1227            "non_existing_eif.eif",
1228            "--debug-mode",
1229        ];
1230        let matches = app.try_get_matches_from(args);
1231        assert!(matches.is_ok());
1232
1233        let result = debug_mode(
1234            matches
1235                .as_ref()
1236                .unwrap()
1237                .subcommand_matches("run-enclave")
1238                .unwrap(),
1239        );
1240        assert!(result);
1241    }
1242
1243    #[test]
1244    fn test_debug_mode_not_supplied() {
1245        let app = create_app!();
1246        let args = vec![
1247            "nitro-cli",
1248            "run-enclave",
1249            "--memory",
1250            "64",
1251            "--cpu-count",
1252            "2",
1253            "--eif-path",
1254            "non_existing_eif.eif",
1255        ];
1256        let matches = app.try_get_matches_from(args);
1257        assert!(matches.is_ok());
1258
1259        let result = debug_mode(
1260            matches
1261                .as_ref()
1262                .unwrap()
1263                .subcommand_matches("run-enclave")
1264                .unwrap(),
1265        );
1266        assert!(!result);
1267    }
1268
1269    #[test]
1270    fn test_attach_console_supplied() {
1271        let app = create_app!();
1272        let args = vec![
1273            "nitro-cli",
1274            "run-enclave",
1275            "--attach-console",
1276            "--memory",
1277            "64",
1278            "--cpu-count",
1279            "2",
1280            "--eif-path",
1281            "non_existing_eif.eif",
1282        ];
1283        let matches = app.try_get_matches_from(args);
1284        assert!(matches.is_ok());
1285
1286        let matches = matches
1287            .as_ref()
1288            .unwrap()
1289            .subcommand_matches("run-enclave")
1290            .unwrap();
1291        let attach_console = attach_console(matches);
1292        let debug_mode = debug_mode(matches);
1293        assert!(attach_console);
1294        assert!(debug_mode);
1295    }
1296
1297    #[test]
1298    fn test_parse_json_config() {
1299        let app = create_app!();
1300        let args = vec![
1301            "nitro-cli",
1302            "run-enclave",
1303            "--config",
1304            "non_existing_config.json",
1305        ];
1306        let matches = app.try_get_matches_from(args);
1307        assert!(matches.is_ok());
1308
1309        let result = parse_config_file(
1310            matches
1311                .as_ref()
1312                .unwrap()
1313                .subcommand_matches("run-enclave")
1314                .unwrap(),
1315        );
1316        assert!(result.is_ok());
1317        assert_eq!(result.unwrap(), "non_existing_config.json");
1318    }
1319}