Skip to main content

stellar_xdr/cli/
guess.rs

1use clap::{Args, ValueEnum};
2use std::cmp;
3use std::ffi::OsString;
4use std::fs::File;
5use std::io::{self, stdin, stdout, Cursor, Read, Write};
6use std::path::Path;
7
8#[derive(thiserror::Error, Debug)]
9#[allow(clippy::enum_variant_names)]
10pub enum Error {
11    #[error("error decoding XDR: {0}")]
12    ReadXdr(#[from] crate::Error),
13    #[error("error reading file: {0}")]
14    ReadFile(std::io::Error),
15    #[error("error writing output: {0}")]
16    WriteOutput(std::io::Error),
17}
18
19#[derive(Args, Debug, Clone)]
20#[command()]
21pub struct Cmd {
22    /// XDR or file containing XDR to decode, or stdin if empty
23    #[arg()]
24    pub input: Option<OsString>,
25
26    // Input format
27    #[arg(long = "input", value_enum, default_value_t)]
28    pub input_format: InputFormat,
29
30    // Output format
31    #[arg(long = "output", value_enum, default_value_t)]
32    pub output_format: OutputFormat,
33
34    /// Certainty as an arbitrary value
35    #[arg(long, default_value = "2")]
36    pub certainty: usize,
37}
38
39#[derive(Default, Clone, Copy, Debug, Eq, Hash, PartialEq, ValueEnum)]
40pub enum InputFormat {
41    Single,
42    #[default]
43    SingleBase64,
44    Stream,
45    StreamBase64,
46    StreamFramed,
47}
48
49#[derive(Default, Clone, Copy, Debug, Eq, Hash, PartialEq, ValueEnum)]
50pub enum OutputFormat {
51    #[default]
52    List,
53}
54
55// TODO: Remove run_x macro, it exists only to reduce the diff from when curr/next
56// channels existed and each had their own run_curr/run_next invocation.
57macro_rules! run_x {
58    ($f:ident) => {
59        fn $f(&self) -> Result<(), Error> {
60            let mut rr = ResetRead::new(self.input()?);
61            let mut guessed = false;
62            'variants: for v in crate::TypeVariant::VARIANTS {
63                rr.reset();
64                let count: usize = match self.input_format {
65                    InputFormat::Single => {
66                        let mut l = crate::Limited::new(&mut rr, crate::Limits::none());
67                        crate::Type::read_xdr_to_end(v, &mut l)
68                            .ok()
69                            .map(|_| 1)
70                            .unwrap_or_default()
71                    }
72                    InputFormat::SingleBase64 => {
73                        let mut l = crate::Limited::new(&mut rr, crate::Limits::none());
74                        crate::Type::read_xdr_base64_to_end(v, &mut l)
75                            .ok()
76                            .map(|_| 1)
77                            .unwrap_or_default()
78                    }
79                    InputFormat::Stream => {
80                        let mut l = crate::Limited::new(&mut rr, crate::Limits::none());
81                        let iter = crate::Type::read_xdr_iter(v, &mut l);
82                        let iter = iter.take(self.certainty);
83                        let mut count = 0;
84                        for v in iter {
85                            match v {
86                                Ok(_) => count += 1,
87                                Err(_) => continue 'variants,
88                            }
89                        }
90                        count
91                    }
92                    InputFormat::StreamBase64 => {
93                        let mut l = crate::Limited::new(&mut rr, crate::Limits::none());
94                        let iter = crate::Type::read_xdr_base64_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::StreamFramed => {
106                        let mut l = crate::Limited::new(&mut rr, crate::Limits::none());
107                        let iter = crate::Type::read_xdr_framed_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                };
119                if count > 0 {
120                    writeln!(stdout(), "{}", v.name()).map_err(Error::WriteOutput)?;
121                    guessed = true;
122                }
123            }
124            if (!guessed) {
125                std::process::exit(1);
126            }
127            Ok(())
128        }
129    };
130}
131
132impl Cmd {
133    /// Run the CLIs guess command.
134    ///
135    /// ## Errors
136    ///
137    /// If the command is configured with state that is invalid.
138    pub fn run(&self) -> Result<(), Error> {
139        let result = self.run_inner();
140        match result {
141            Ok(()) => Ok(()),
142            Err(Error::WriteOutput(e)) if e.kind() == std::io::ErrorKind::BrokenPipe => Ok(()),
143            Err(e) => Err(e),
144        }
145    }
146
147    run_x!(run_inner);
148
149    fn input(&self) -> Result<Box<dyn Read>, Error> {
150        if let Some(input) = &self.input {
151            let exist = Path::new(input).try_exists();
152            if let Ok(true) = exist {
153                Ok(Box::new(File::open(input).map_err(Error::ReadFile)?))
154            } else {
155                Ok(Box::new(Cursor::new(input.clone().into_encoded_bytes())))
156            }
157        } else {
158            Ok(Box::new(stdin()))
159        }
160    }
161}
162
163struct ResetRead<R: Read> {
164    read: R,
165    buf: Vec<u8>,
166    cursor: usize,
167}
168
169impl<R: Read> ResetRead<R> {
170    fn new(r: R) -> Self {
171        Self {
172            read: r,
173            buf: Vec::new(),
174            cursor: 0,
175        }
176    }
177
178    fn reset(&mut self) {
179        self.cursor = 0;
180    }
181}
182
183impl<R: Read> Read for ResetRead<R> {
184    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
185        // Read from the buffer first into buf.
186        let n = cmp::min(self.buf.len() - self.cursor, buf.len());
187        buf[..n].copy_from_slice(&self.buf[self.cursor..self.cursor + n]);
188        // Read from the reader and cache the result in the buf if the buf is consumed.
189        if n < buf.len() {
190            let read_n = self.read.read(&mut buf[n..])?;
191            self.buf.extend_from_slice(&buf[n..n + read_n]);
192            self.cursor += n + read_n;
193            Ok(n + read_n)
194        } else {
195            self.cursor += n;
196            Ok(n)
197        }
198    }
199}
200
201#[cfg(test)]
202mod test {
203    use std::{
204        error,
205        io::{Cursor, Read},
206    };
207
208    use super::ResetRead;
209
210    #[test]
211    fn test_reset_read() -> Result<(), Box<dyn error::Error>> {
212        let source: Vec<u8> = (0..8).collect();
213        let reader = Cursor::new(source);
214        let mut rr = ResetRead::new(reader);
215
216        let mut buf = [0u8; 4];
217        let n = rr.read(&mut buf)?;
218        assert_eq!(n, 4);
219        assert_eq!(buf, [0, 1, 2, 3]);
220
221        let mut buf = [0u8; 4];
222        let n = rr.read(&mut buf)?;
223        assert_eq!(n, 4);
224        assert_eq!(buf, [4, 5, 6, 7]);
225
226        let n = rr.read(&mut buf)?;
227        assert_eq!(n, 0);
228
229        rr.reset();
230        let mut buf = [0u8; 4];
231        let n = rr.read(&mut buf)?;
232        assert_eq!(n, 4);
233        assert_eq!(buf, [0, 1, 2, 3]);
234
235        Ok(())
236    }
237
238    // Test that a read after reset() works correctly when partially
239    // overlapping the cached buffer. Previously this panicked with
240    // "range end index 5 out of range for slice of length 4".
241    #[test]
242    fn test_reset_read_partial_cache_overlap() -> Result<(), Box<dyn error::Error>> {
243        // 12 bytes with distinct values to verify read ordering.
244        let source: Vec<u8> = vec![
245            0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
246        ];
247        let reader = Cursor::new(source);
248        let mut rr = ResetRead::new(reader);
249
250        // Read 5 bytes to populate cache
251        let mut buf5 = [0u8; 5];
252        let n = rr.read(&mut buf5)?;
253        assert_eq!(n, 5);
254        assert_eq!(buf5, [0x00, 0x00, 0x00, 0x00, 0x01]);
255
256        // Reset cursor to replay from start
257        rr.reset();
258
259        // Read 4 bytes entirely from cache
260        let mut buf4 = [0u8; 4];
261        let n = rr.read(&mut buf4)?;
262        assert_eq!(n, 4);
263        assert_eq!(buf4, [0x00, 0x00, 0x00, 0x00]);
264
265        // Read 4 bytes: 1 from cache, 3 from the underlying reader.
266        let mut buf4 = [0u8; 4];
267        let n = rr.read(&mut buf4)?;
268        assert_eq!(n, 4);
269        assert_eq!(buf4, [0x01, 0x02, 0x03, 0x04]);
270
271        // Read remaining 3 bytes
272        let mut buf3 = [0u8; 3];
273        let n = rr.read(&mut buf3)?;
274        assert_eq!(n, 3);
275        assert_eq!(buf3, [0x05, 0x06, 0x07]);
276
277        // Read last byte
278        let mut buf1 = [0u8; 1];
279        let n = rr.read(&mut buf1)?;
280        assert_eq!(n, 1);
281        assert_eq!(buf1, [0x08]);
282
283        Ok(())
284    }
285}