Skip to main content

soil_cli/params/
message_params.rs

1// This file is part of Soil.
2
3// Copyright (C) Soil contributors.
4// Copyright (C) Parity Technologies (UK) Ltd.
5// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
6
7//! Params to configure how a message should be passed into a command.
8
9use crate::error::Error;
10use array_bytes::{hex2bytes, hex_bytes2hex_str};
11use clap::Args;
12use std::io::BufRead;
13
14/// Params to configure how a message should be passed into a command.
15#[derive(Debug, Clone, Args)]
16pub struct MessageParams {
17	/// Message to process. Will be read from STDIN otherwise.
18	/// The message is assumed to be raw bytes per default. Use `--hex` for hex input. Can
19	/// optionally be prefixed with `0x` in the hex case.
20	#[arg(long)]
21	message: Option<String>,
22
23	/// The message is hex-encoded data.
24	#[arg(long)]
25	hex: bool,
26}
27
28impl MessageParams {
29	/// Produces the message by either using its immediate value or reading from stdin.
30	///
31	/// This function should only be called once and the result cached.
32	pub(crate) fn message_from<F, R>(&self, create_reader: F) -> Result<Vec<u8>, Error>
33	where
34		R: BufRead,
35		F: FnOnce() -> R,
36	{
37		let raw = match &self.message {
38			Some(raw) => raw.as_bytes().to_vec(),
39			None => {
40				let mut raw = vec![];
41				create_reader().read_to_end(&mut raw)?;
42				raw
43			},
44		};
45		if self.hex {
46			hex2bytes(hex_bytes2hex_str(&raw)?).map_err(Into::into)
47		} else {
48			Ok(raw)
49		}
50	}
51}
52
53#[cfg(test)]
54mod tests {
55	use super::*;
56
57	/// Test that decoding an immediate message works.
58	#[test]
59	fn message_decode_immediate() {
60		for (name, input, hex, output) in test_closures() {
61			println!("Testing: immediate_{}", name);
62			let params = MessageParams { message: Some(input.into()), hex };
63			let message = params.message_from(|| std::io::stdin().lock());
64
65			match output {
66				Some(output) => {
67					let message = message.expect(&format!("{}: should decode but did not", name));
68					assert_eq!(message, output, "{}: decoded a wrong message", name);
69				},
70				None => {
71					message.err().expect(&format!("{}: should not decode but did", name));
72				},
73			}
74		}
75	}
76
77	/// Test that decoding a message from a stream works.
78	#[test]
79	fn message_decode_stream() {
80		for (name, input, hex, output) in test_closures() {
81			println!("Testing: stream_{}", name);
82			let params = MessageParams { message: None, hex };
83			let message = params.message_from(|| input.as_bytes());
84
85			match output {
86				Some(output) => {
87					let message = message.expect(&format!("{}: should decode but did not", name));
88					assert_eq!(message, output, "{}: decoded a wrong message", name);
89				},
90				None => {
91					message.err().expect(&format!("{}: should not decode but did", name));
92				},
93			}
94		}
95	}
96
97	/// Returns (test_name, input, hex, output).
98	fn test_closures() -> Vec<(&'static str, &'static str, bool, Option<&'static [u8]>)> {
99		vec![
100			("decode_no_hex_works", "Hello this is not hex", false, Some(b"Hello this is not hex")),
101			("decode_no_hex_with_hex_string_works", "0xffffffff", false, Some(b"0xffffffff")),
102			("decode_hex_works", "0x00112233", true, Some(&[0, 17, 34, 51])),
103			("decode_hex_without_prefix_works", "00112233", true, Some(&[0, 17, 34, 51])),
104			("decode_hex_uppercase_works", "0xaAbbCCDd", true, Some(&[170, 187, 204, 221])),
105			("decode_hex_wrong_len_errors", "0x0011223", true, None),
106		]
107	}
108}