#字节序 #解析器 #网络

scalar_types

一个封装标量类型的端序安全类型的模块

2 个版本

0.1.1 2023年2月21日
0.1.0 2023年2月21日

#2303 in Rust 模式

GPL-3.0-only

23KB
133

此库旨在帮助解析端序敏感的内容。它允许我们像处理本地端序一样正常解析数据。然后,当需要值时,我们可以将值转换为所需的端序。

这使我们免于使用 T::from_xx_bytes 创建条件解析,而是允许我们懒惰地解析值。然后在需要时进行转换。这有点像端序的“叠加”。

由于它还封装了所有基本标量类型,因此它为我们提供了一个通用的类型,可以传递给标记数据为端序敏感的函数。

还包括一些辅助函数,可以更快地从流中加载值。

让我们看看一个基本的使用场景。我们有一个文件,它可能是在大端或小端系统上创建的。文件规范的实现说明了文件中的第一个字节将指示文件其余部分是大端还是小端。假设 0x00 为小端,0x01 为大端。

让我们创建一个程序,解析第一个字节并存储文件的端序。然后使用该端序将文件中读取的值转换为所需的端序。

use scalar_types::Endian;
use std::io::{BufReader, Result};
  
fn read_some_stuff() -> Result<()> {
    // Binary file contains 01 | 00 00 00 02 .. ..
    //      Big Endian Flag-^    ^-Big Endian 0x2
    // System endianness is little endian
    let file = std::fs::File::open("file.bin")?;
    let mut reader = BufReader::new(file);
 
    // File endianness changes based on system that made it
    // The first byte in the file determines the endianness; 
    // 0 = little, 1 = big
    let mut buffer = vec![0u8];
    reader.read_exact(&mut buffer)?;
 
    // We can then store the endianness as a variable
    // and use this to cast. Allowing us to dynamically
    // cast the data based on the content of the file
    let endianness = {
        if buffer[0] == 0 { 
            Endian::Little(()) 
        } else { 
            Endian::Big(()) 
        }
    };
 
    // Try and read the endian sensitive content normally from stream
    // Endian values are Endian::Native by default
    let endian_value = Endian::<u32>::from_stream(&mut reader);
    let parsed_value = match endian_value {
        Some(value) => value,
        None => panic!("Unable to parse value from stream!")
    };
 
    // Then we convert the value only when we need it.
    // Saving us from having to cast u32::from_xx_bytes for every value
    let expanded_value = match parsed_value.cast(endianness) {
        Some(value) => value,
        None => 0u32
    };
 
    assert_eq!(expanded_value, 2);
    Ok(())
}

我们还可以反过来操作!让我们使用 Endian 创建我们刚刚解析的文件。我们可以很容易地使用 to_le_bytes 和 to_be_bytes;但是,这样做意味着我们需要根据我们针对的系统在两者之间交替。相反,我们可以只存储目标端序并使用相同的代码。通过存储的变量动态切换端序。这要干净得多。

而不是使用 to_le_bytes 或 to_be_bytes;我们将调用 to_ne_bytes,让 Endian 处理转换。

use scalar_types::Endian;
use std::io::{Result, Write};
use std::fs::File;
use std::path::Path;
fn write_some_stuff() -> Result<()> {
    // Open a file hand for our output
    let mut output = File::create(Path::new("file.bin"))?;
 
    // since we're a little endian system, writing a big endian file
    // we will assign this value explicitly.
    // let's store this for later to use it to cast
    let endianness = Endian::Big(());
 
    // Note that Endian::Native isn't actually aware of the system endianness,
    // it instead acts as an abstract container like a "superposition". 
    // If we wanted to get our native endianness "get_native_endianness()"
    // is a helper function that will return the correct endianness.
    // let endianness = get_native_endianness()?;
 
    // Moving forward:
    // Our spec says we need to write 0x01 for big endian, or 0x00 for little.
    if endianness.is_big() {
        write!(output, "\x01")?;
    } else {
        write!(output, "\x00")?;
    };
 
    // Next we're going to create our native endian 0x02 value
    let endian_value = Endian::new(2u32);
 
    // Finally we write the output
    if let Some(value) = endian_value.cast(endian_value) {
        // We've already handled endianness, so we will use the built-in to_ne_bytes function
        output.write(&value.to_ne_bytes())?;
    }
 
    Ok(())
}

这是为了帮助我创建用于视频游戏的修改工具而创建的。由于一些游戏共享相同的数据,但端序根据构建的控制台而变化。以这种方式工作允许我使用相同的代码为所有系统编写,并在需要时动态地将值转换为本地端序。我想,也许其他人可能会发现它有用。

无运行时依赖