stellar_xdr/cli/
guess.rs

1use clap::{Args, ValueEnum};
2use std::ffi::OsString;
3use std::fs::File;
4use std::io::{stdin, Cursor};
5use std::path::Path;
6use std::{
7    cmp,
8    io::{self, Read},
9};
10
11use crate::cli::Channel;
12
13#[derive(thiserror::Error, Debug)]
14#[allow(clippy::enum_variant_names)]
15pub enum Error {
16    #[error("error decoding XDR: {0}")]
17    ReadXdrCurr(#[from] crate::curr::Error),
18    #[error("error decoding XDR: {0}")]
19    ReadXdrNext(#[from] crate::next::Error),
20    #[error("error reading file: {0}")]
21    ReadFile(#[from] std::io::Error),
22}
23
24#[derive(Args, Debug, Clone)]
25#[command()]
26pub struct Cmd {
27    /// XDR or file containing XDR to decode, or stdin if empty
28    #[arg()]
29    pub input: Option<OsString>,
30
31    // Input format
32    #[arg(long = "input", value_enum, default_value_t)]
33    pub input_format: InputFormat,
34
35    // Output format
36    #[arg(long = "output", value_enum, default_value_t)]
37    pub output_format: OutputFormat,
38
39    /// Certainty as an arbitrary value
40    #[arg(long, default_value = "2")]
41    pub certainty: usize,
42}
43
44#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, ValueEnum)]
45pub enum InputFormat {
46    Single,
47    SingleBase64,
48    Stream,
49    StreamBase64,
50    StreamFramed,
51}
52
53impl Default for InputFormat {
54    fn default() -> Self {
55        Self::SingleBase64
56    }
57}
58
59#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, ValueEnum)]
60pub enum OutputFormat {
61    List,
62}
63
64impl Default for OutputFormat {
65    fn default() -> Self {
66        Self::List
67    }
68}
69
70macro_rules! run_x {
71    ($f:ident, $m:ident) => {
72        fn $f(&self) -> Result<(), Error> {
73            let mut rr = ResetRead::new(self.input()?);
74            let mut guessed = false;
75            'variants: for v in crate::$m::TypeVariant::VARIANTS {
76                rr.reset();
77                let count: usize = match self.input_format {
78                    InputFormat::Single => {
79                        let mut l = crate::$m::Limited::new(&mut rr, crate::$m::Limits::none());
80                        crate::$m::Type::read_xdr_to_end(v, &mut l)
81                            .ok()
82                            .map(|_| 1)
83                            .unwrap_or_default()
84                    }
85                    InputFormat::SingleBase64 => {
86                        let mut l = crate::$m::Limited::new(&mut rr, crate::$m::Limits::none());
87                        crate::$m::Type::read_xdr_base64_to_end(v, &mut l)
88                            .ok()
89                            .map(|_| 1)
90                            .unwrap_or_default()
91                    }
92                    InputFormat::Stream => {
93                        let mut l = crate::$m::Limited::new(&mut rr, crate::$m::Limits::none());
94                        let iter = crate::$m::Type::read_xdr_iter(v, &mut l);
95                        let iter = iter.take(self.certainty);
96                        let mut count = 0;
97                        for v in iter {
98                            match v {
99                                Ok(_) => count += 1,
100                                Err(_) => continue 'variants,
101                            }
102                        }
103                        count
104                    }
105                    InputFormat::StreamBase64 => {
106                        let mut l = crate::$m::Limited::new(&mut rr, crate::$m::Limits::none());
107                        let iter = crate::$m::Type::read_xdr_base64_iter(v, &mut l);
108                        let iter = iter.take(self.certainty);
109                        let mut count = 0;
110                        for v in iter {
111                            match v {
112                                Ok(_) => count += 1,
113                                Err(_) => continue 'variants,
114                            }
115                        }
116                        count
117                    }
118                    InputFormat::StreamFramed => {
119                        let mut l = crate::$m::Limited::new(&mut rr, crate::$m::Limits::none());
120                        let iter = crate::$m::Type::read_xdr_framed_iter(v, &mut l);
121                        let iter = iter.take(self.certainty);
122                        let mut count = 0;
123                        for v in iter {
124                            match v {
125                                Ok(_) => count += 1,
126                                Err(_) => continue 'variants,
127                            }
128                        }
129                        count
130                    }
131                };
132                if count > 0 {
133                    println!("{}", v.name());
134                    guessed = true;
135                }
136            }
137            if (!guessed) {
138                std::process::exit(1);
139            }
140            Ok(())
141        }
142    };
143}
144
145impl Cmd {
146    /// Run the CLIs guess command.
147    ///
148    /// ## Errors
149    ///
150    /// If the command is configured with state that is invalid.
151    pub fn run(&self, channel: &Channel) -> Result<(), Error> {
152        match channel {
153            Channel::Curr => self.run_curr()?,
154            Channel::Next => self.run_next()?,
155        }
156        Ok(())
157    }
158
159    run_x!(run_curr, curr);
160    run_x!(run_next, next);
161
162    fn input(&self) -> Result<Box<dyn Read>, Error> {
163        if let Some(input) = &self.input {
164            let exist = Path::new(input).try_exists();
165            if let Ok(true) = exist {
166                Ok(Box::new(File::open(input)?))
167            } else {
168                Ok(Box::new(Cursor::new(input.clone().into_encoded_bytes())))
169            }
170        } else {
171            Ok(Box::new(stdin()))
172        }
173    }
174}
175
176struct ResetRead<R: Read> {
177    read: R,
178    buf: Vec<u8>,
179    cursor: usize,
180}
181
182impl<R: Read> ResetRead<R> {
183    fn new(r: R) -> Self {
184        Self {
185            read: r,
186            buf: Vec::new(),
187            cursor: 0,
188        }
189    }
190
191    fn reset(&mut self) {
192        self.cursor = 0;
193    }
194}
195
196impl<R: Read> Read for ResetRead<R> {
197    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
198        // Read from the buffer first into buf.
199        let n = cmp::min(self.buf.len() - self.cursor, buf.len());
200        buf[..n].copy_from_slice(&self.buf[self.cursor..self.cursor + n]);
201        // Read from the reader and cache the result in the buf if the buf is consumed.
202        if n < buf.len() {
203            let read_n = self.read.read(buf)?;
204            self.buf.extend_from_slice(&buf[n..n + read_n]);
205            self.cursor += n + read_n;
206            Ok(n + read_n)
207        } else {
208            self.cursor += n;
209            Ok(n)
210        }
211    }
212}
213
214#[cfg(test)]
215mod test {
216    use std::{
217        error,
218        io::{Cursor, Read},
219    };
220
221    use super::ResetRead;
222
223    #[test]
224    fn test_reset_read() -> Result<(), Box<dyn error::Error>> {
225        let source: Vec<u8> = (0..8).collect();
226        let reader = Cursor::new(source);
227        let mut rr = ResetRead::new(reader);
228
229        let mut buf = [0u8; 4];
230        let n = rr.read(&mut buf)?;
231        assert_eq!(n, 4);
232        assert_eq!(buf, [0, 1, 2, 3]);
233
234        let mut buf = [0u8; 4];
235        let n = rr.read(&mut buf)?;
236        assert_eq!(n, 4);
237        assert_eq!(buf, [4, 5, 6, 7]);
238
239        let n = rr.read(&mut buf)?;
240        assert_eq!(n, 0);
241
242        rr.reset();
243        let mut buf = [0u8; 4];
244        let n = rr.read(&mut buf)?;
245        assert_eq!(n, 4);
246        assert_eq!(buf, [0, 1, 2, 3]);
247
248        Ok(())
249    }
250}