P3KI / bendy

A rust library for encoding and decoding bencode with enforced cannonicalization rules.
BSD 3-Clause "New" or "Revised" License
70 stars 13 forks source link

Implement `Clone` for `Object` and `DictDecoder` #60

Closed avdb13 closed 1 year ago

avdb13 commented 1 year ago

Currently I have the following code in which I have two possible enumerations for an object, first I have to parse the bytes to figure out what object to initialize for the data and after that I have to parse the fields. I need the ability to clone either Object or DictDecoder for this:


impl FromBencode for Info {
    const EXPECTED_RECURSION_DEPTH: usize = 999;

    fn decode_bencode_object(
        object: bendy::decoding::Object,
    ) -> Result<Self, bendy::decoding::Error>
    where
        Self: Sized,
    {
        let dict = object.try_into_dictionary()?;
        let mut info = Info::default();

        // figure out what mode the info type is but gets consumed so borrow checker complains after this point
        info.mode = info.try_mode(dict).unwrap();

        match info.mode {
            // ...
        }
}
thequux commented 1 year ago

Alas, making the decoder clonable will be a significant amount of work; all of the various types of decoder have a shared reference to the main decoding state, and just cloning the DictDecoder/ListDecoder leaves us with no place to store the main state.

However, all is not lost. Once you've decoded the dictionary and found the mode field, you can call into_raw on the DictDecoder object, which will return a reference to the raw representation of the entire dict. You can use that to create a new decoder, the first object returned from which will be the original dict.

It would also be possible to "reset" a dictionary or list decoder, so that the next item that it returns would be the first one. There's no code for that yet, but we've just added it to the TODO list.

Would either of these two solutions work for you?

avdb13 commented 1 year ago

Can you suggest an example? This is what I have currently, spent hours trying to work around borrowing problems:

        let dict = object.try_into_dictionary()?;
        let mut info = Info::default();

        // determine the mode while taking ownership of all the elements
        let f = |dict: DictDecoder| -> (Vec<_>, Mode) {
            let mut tmp = Vec::new();
            let mut mode = Mode::default();
            while let Some(pair) = dict.next_pair().unwrap() {
                match pair {
                    (b"files", _) => mode = Mode::Multi(MultiInfo::default()),
                    p => tmp.push(p),
                }
            }

            (tmp, mode)
        };
        let result = f(dict);

        // match the vector of Objects on the right field
        let rem: Vec<Result<Option<&(&[u8], bendy::decoding::Object)>, bendy::encoding::Error>> = result.0.iter().map(|pair| {
            match pair {
            (b"piece length", _) => {
                match u64::decode_bencode_object(pair.1) {
                    Ok(x) => {
                        info.piece_length = x;
                        Ok(None)
                    },
                    Err(e) => Err(e),
                }
            }
            (b"pieces", _) => {
                match pair.1.try_into_bytes() {
                    Ok(x) => {
                        info.pieces = x.to_vec();
                        Ok(None)
                    },
                    Err(e) => Err(e.into()),
                }
            }
            (b"private", _) => {
                match u8::decode_bencode_object(pair.1) {
                    Ok(0) => {
                        info.private = Some(());
                        Ok(None)
                    },
                    Ok(x) => {
                        Ok(None)
                    },
                    Err(e) => Err(e.into()),
                }
            }
            p => Ok(Some(p)),
        }
        }).collect();

Sorry for the ugly Result handling boilerplate. I'm not sure whether it's a good idea to define the match in another closure and run that over the Iterator (which can't be constructed and I have no clue why).

avdb13 commented 1 year ago

Note that this is inside an impl FromBencode.