earthbucks_lib 0.8.5

EarthBucks library for data structures and algorithms
Documentation
use crate::buf::EbxBuf;
use crate::buf_reader::BufReader;
use crate::buf_writer::BufWriter;
use crate::error::EbxError;
use crate::opcode::{Opcode, OP, OPCODE_TO_NAME};

#[derive(Debug, PartialEq, Clone)]
pub struct ScriptChunk {
    pub opcode: u8,
    pub buffer: Option<Vec<u8>>,
}

impl ScriptChunk {
    pub fn new(opcode: u8, arr: Option<Vec<u8>>) -> ScriptChunk {
        ScriptChunk {
            opcode,
            buffer: arr,
        }
    }

    pub fn get_data(&self) -> Result<Vec<u8>, EbxError> {
        if self.opcode == Opcode::OP_1NEGATE {
            return Ok(vec![0x80]);
        } else if self.opcode == Opcode::OP_0 {
            return Ok(vec![]);
        } else if Opcode::OP_1 <= self.opcode && self.opcode <= Opcode::OP_16 {
            return Ok(vec![self.opcode - Opcode::OP_1 + 1]);
        }
        match &self.buffer {
            Some(buffer) => Ok(buffer.clone()),
            None => Err(EbxError::NotEnoughDataError { source: None }),
        }
    }

    pub fn to_strict_str(&self) -> Result<String, EbxError> {
        match &self.buffer {
            Some(buffer) => {
                let hex: Vec<String> = buffer.iter().map(|b| format!("{:02x}", b)).collect();
                Ok(format!("0x{}", hex.join("")))
            }
            None => {
                let name = OPCODE_TO_NAME.get(&self.opcode);
                match name {
                    Some(name) => Ok(name.to_string()),
                    None => Err(EbxError::InvalidOpcodeError { source: None }),
                }
            }
        }
    }

    pub fn from_strict_str(str: String) -> Result<ScriptChunk, EbxError> {
        let mut chunk = ScriptChunk::new(0, None);
        if str.starts_with("0x") {
            let buffer = Vec::<u8>::from_strict_hex(str.strip_prefix("0x").unwrap())
                .map_err(|_| EbxError::InvalidHexError { source: None })?;
            let len = buffer.len();
            chunk.buffer = Some(buffer);
            if len <= 0xff {
                chunk.opcode = Opcode::OP_PUSHDATA1;
            } else if len <= 0xffff {
                chunk.opcode = Opcode::OP_PUSHDATA2;
            } else if len <= 0xffffffff {
                chunk.opcode = Opcode::OP_PUSHDATA4;
            } else {
                return Err(EbxError::TooMuchDataError { source: None });
            }
        } else {
            let opcode = OP.get(&str.as_str());
            match opcode {
                Some(opcode) => chunk.opcode = *opcode,
                None => return Err(EbxError::InvalidOpcodeError { source: None }),
            }
            chunk.buffer = None;
        }
        Ok(chunk)
    }

    pub fn to_buf(&self) -> Vec<u8> {
        let mut result = Vec::new();
        result.push(self.opcode);
        match &self.buffer {
            Some(buffer) => {
                let len = buffer.len();
                if self.opcode == Opcode::OP_PUSHDATA1 {
                    let mut writer = BufWriter::new();
                    writer.write_u8(len as u8);
                    result.extend_from_slice(&writer.to_buf());
                    result.extend_from_slice(buffer);
                } else if self.opcode == Opcode::OP_PUSHDATA2 {
                    let mut writer = BufWriter::new();
                    writer.write_u16_be(len as u16);
                    result.extend_from_slice(&writer.to_buf());
                    result.extend_from_slice(buffer);
                } else if self.opcode == Opcode::OP_PUSHDATA4 {
                    let mut writer = BufWriter::new();
                    writer.write_u32_be(len as u32);
                    result.extend_from_slice(&writer.to_buf());
                    result.extend_from_slice(buffer);
                }
            }
            None => (),
        }
        result
    }

    pub fn from_buf(buf: Vec<u8>) -> Result<ScriptChunk, EbxError> {
        let mut reader = BufReader::new(buf);
        ScriptChunk::from_buf_reader(&mut reader)
    }

    pub fn from_buf_reader(reader: &mut BufReader) -> Result<ScriptChunk, EbxError> {
        let opcode = reader.read_u8()?;
        let mut chunk = ScriptChunk::new(opcode, None);
        if opcode == Opcode::OP_PUSHDATA1 {
            let len = reader.read_u8()? as usize;
            chunk.buffer = Some(reader.read(len)?);
            if len == 0 || (len == 1 && (1..=16).contains(&chunk.buffer.as_ref().unwrap()[0])) {
                return Err(EbxError::NonMinimalEncodingError { source: None });
            }
        } else if opcode == Opcode::OP_PUSHDATA2 {
            let len = reader.read_u16_be()? as usize;
            if len <= 0xff {
                return Err(EbxError::NonMinimalEncodingError { source: None });
            }
            chunk.buffer = Some(reader.read(len)?);
        } else if opcode == Opcode::OP_PUSHDATA4 {
            let len = reader.read_u32_be()? as usize;
            if len <= 0xffff {
                return Err(EbxError::NonMinimalEncodingError { source: None });
            }
            chunk.buffer = Some(reader.read(len)?);
        }
        Ok(chunk)
    }

    pub fn from_data(data: Vec<u8>) -> ScriptChunk {
        let len = data.len();
        if len == 0 {
            ScriptChunk::new(Opcode::OP_0, None)
        } else if len == 1 && 1 <= data[0] && data[0] <= 16 {
            ScriptChunk::new(data[0] + Opcode::OP_1 - 1, None)
        } else if len <= 0xff {
            ScriptChunk::new(Opcode::OP_PUSHDATA1, Some(data))
        } else if len <= 0xffff {
            ScriptChunk::new(Opcode::OP_PUSHDATA2, Some(data))
        } else if len <= 0xffffffff {
            ScriptChunk::new(Opcode::OP_PUSHDATA4, Some(data))
        } else {
            ScriptChunk::new(0, None)
        }
    }

    pub fn from_small_number(n: i8) -> ScriptChunk {
        if n == -1 || (1..=16).contains(&n) {
            ScriptChunk::new(n as u8 + Opcode::OP_1 - 1, None)
        } else {
            ScriptChunk::new(0, None)
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_new() {
        let opcode = 1;
        let buffer = Some(vec![2, 3, 4]);
        let chunk = ScriptChunk::new(opcode, buffer.clone());

        assert_eq!(chunk.opcode, opcode);
        assert_eq!(chunk.buffer, buffer);
    }

    #[test]
    fn test_to_string_if() {
        let chunk = ScriptChunk::new(Opcode::OP_IF, None);
        assert_eq!(chunk.to_strict_str().unwrap(), "IF");
    }

    #[test]
    fn test_to_string_pushdata1() {
        let chunk = ScriptChunk::new(Opcode::OP_PUSHDATA1, Some(vec![1, 2, 3]));
        assert_eq!(chunk.to_strict_str().unwrap(), "0x010203");
    }

    #[test]
    fn test_to_string_pushdata2() {
        let chunk = ScriptChunk::new(Opcode::OP_PUSHDATA2, Some(vec![4, 5, 6]));
        assert_eq!(chunk.to_strict_str().unwrap(), "0x040506");
    }

    #[test]
    fn test_to_string_pushdata4() {
        let chunk = ScriptChunk::new(Opcode::OP_PUSHDATA4, Some(vec![7, 8, 9]));
        assert_eq!(chunk.to_strict_str().unwrap(), "0x070809");
    }

    #[test]
    fn test_from_strict_str_if() {
        let chunk = ScriptChunk::from_strict_str("IF".to_string()).unwrap();
        assert_eq!(chunk.opcode, Opcode::OP_IF);
        assert_eq!(chunk.buffer, None);
    }

    #[test]
    fn test_from_strict_str_pushdata1() {
        let chunk = ScriptChunk::from_strict_str("0x010203".to_string()).unwrap();
        assert_eq!(chunk.opcode, Opcode::OP_PUSHDATA1);
        assert_eq!(chunk.buffer, Some(vec![1, 2, 3]));
    }

    #[test]
    fn test_from_strict_str_pushdata2() {
        let chunk = ScriptChunk::from_strict_str("0x".to_string() + &"01".repeat(256)).unwrap();
        assert_eq!(chunk.opcode, Opcode::OP_PUSHDATA2);
        assert_eq!(chunk.buffer, Some(vec![1; 256]));
    }

    #[test]
    fn test_from_strict_str_pushdata4() {
        let chunk = ScriptChunk::from_strict_str("0x".to_string() + &"01".repeat(70000)).unwrap();
        assert_eq!(chunk.opcode, Opcode::OP_PUSHDATA4);
        assert_eq!(chunk.buffer, Some(vec![1; 70000]));
    }

    #[test]
    fn test_from_strict_str_new() {
        let chunk = ScriptChunk::from_strict_str("0x010203".to_string()).unwrap();
        assert_eq!(chunk.opcode, Opcode::OP_PUSHDATA1);
        assert_eq!(chunk.buffer, Some(vec![1, 2, 3]));
    }

    #[test]
    fn test_to_buf_if() {
        let chunk = ScriptChunk::new(Opcode::OP_IF, None);
        assert_eq!(chunk.to_buf(), vec![Opcode::OP_IF]);
    }

    #[test]
    fn test_to_buf_pushdata1() {
        let chunk = ScriptChunk::new(Opcode::OP_PUSHDATA1, Some(vec![1, 2, 3]));
        assert_eq!(chunk.to_buf(), vec![Opcode::OP_PUSHDATA1, 3, 1, 2, 3]);
    }

    #[test]
    fn test_to_buf_pushdata2() {
        let chunk = ScriptChunk::new(Opcode::OP_PUSHDATA2, Some(vec![1; 256]));
        let mut expected = vec![Opcode::OP_PUSHDATA2, 1, 0];
        expected.extend(vec![1; 256]);
        assert_eq!(chunk.to_buf(), expected);
    }

    #[test]
    fn test_to_buf_pushdata4() {
        let chunk = ScriptChunk::new(Opcode::OP_PUSHDATA4, Some(vec![1; 65536]));
        let mut expected = vec![Opcode::OP_PUSHDATA4, 0, 1, 0, 0];
        expected.extend(vec![1; 65536]);
        assert_eq!(chunk.to_buf(), expected);
    }

    #[test]
    fn test_from_buf_if() {
        let arr = vec![Opcode::OP_IF];
        let chunk = ScriptChunk::from_buf(arr).unwrap();
        assert_eq!(chunk.opcode, Opcode::OP_IF);
        assert_eq!(chunk.buffer, None);
    }

    #[test]
    fn test_from_buf_pushdata1() {
        let mut arr = vec![Opcode::OP_PUSHDATA1, 2];
        arr.extend(vec![1, 2]);
        let chunk = ScriptChunk::from_buf(arr).unwrap();
        assert_eq!(chunk.opcode, Opcode::OP_PUSHDATA1);
        assert_eq!(chunk.buffer, Some(vec![1, 2]));
    }

    #[test]
    fn test_from_buf_pushdata2() {
        let mut arr = vec![Opcode::OP_PUSHDATA2, 0x01, 0x00];
        arr.extend(vec![0; 256]);
        let chunk = ScriptChunk::from_buf(arr).unwrap();
        assert_eq!(chunk.opcode, Opcode::OP_PUSHDATA2);
        assert_eq!(chunk.buffer, Some(vec![0; 256]));
    }

    #[test]
    fn test_from_buf_pushdata4() {
        let mut arr = vec![Opcode::OP_PUSHDATA4, 0, 0x01, 0, 0];
        arr.extend(vec![0; 0x010000]);
        let chunk = ScriptChunk::from_buf(arr).unwrap();
        assert_eq!(chunk.opcode, Opcode::OP_PUSHDATA4);
        assert_eq!(chunk.buffer, Some(vec![0; 0x010000]));
    }

    #[test]
    fn test_from_buf_new_pushdata1() {
        let mut arr = vec![Opcode::OP_PUSHDATA1, 2];
        arr.extend(vec![1, 2]);
        let chunk = ScriptChunk::from_buf(arr).unwrap();
        assert_eq!(chunk.opcode, Opcode::OP_PUSHDATA1);
        assert_eq!(chunk.buffer, Some(vec![1, 2]));
    }

    #[test]
    fn test_from_data() {
        let data = vec![1, 2, 3];
        let chunk = ScriptChunk::from_data(data.clone());
        assert_eq!(chunk.opcode, Opcode::OP_PUSHDATA1);
        assert_eq!(chunk.buffer, Some(data));
    }

    #[test]
    fn test_from_data_pushdata2() {
        let data = vec![1; 256];
        let chunk = ScriptChunk::from_data(data.clone());
        assert_eq!(chunk.opcode, Opcode::OP_PUSHDATA2);
        assert_eq!(chunk.buffer, Some(data));
    }

    #[test]
    fn test_from_data_pushdata4() {
        let data = vec![1; 65536];
        let chunk = ScriptChunk::from_data(data.clone());
        assert_eq!(chunk.opcode, Opcode::OP_PUSHDATA4);
        assert_eq!(chunk.buffer, Some(data));
    }

    #[test]
    fn test_from_buf_pushdata1_error() {
        let arr = vec![Opcode::OP_PUSHDATA1, 2];
        let result = ScriptChunk::from_buf(arr);
        assert!(
            result.is_err(),
            "Expected an error for insufficient buffer length in PUSHDATA1 case"
        );
        match result {
            Err(e) => assert_eq!(e.to_string(), "not enough bytes in the buffer to read"),
            _ => panic!("Expected an error for insufficient buffer length in PUSHDATA1 case"),
        }
    }

    #[test]
    fn test_from_buf_pushdata2_error() {
        let arr = vec![Opcode::OP_PUSHDATA2, 0, 2];
        let result = ScriptChunk::from_buf(arr);
        assert!(
            result.is_err(),
            "Expected an error for insufficient buffer length in PUSHDATA2 case"
        );
        match result {
            Err(e) => assert_eq!(e.to_string(), "non-minimal encoding"),
            _ => panic!("Expected an error for insufficient buffer length in PUSHDATA2 case"),
        }
    }

    #[test]
    fn test_from_buf_pushdata4_error() {
        let arr = vec![Opcode::OP_PUSHDATA4, 0, 0, 0, 2];
        let result = ScriptChunk::from_buf(arr);
        assert!(
            result.is_err(),
            "Expected an error for insufficient buffer length in PUSHDATA4 case"
        );
        match result {
            Err(e) => assert_eq!(e.to_string(), "non-minimal encoding"),
            _ => panic!("Expected an error for insufficient buffer length in PUSHDATA4 case"),
        }
    }
}