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