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