简体   繁体   English

将类 C 的打包数据结构映射到 Rust 结构

[英]Map C-like packed data structure to Rust struct

I'm fairly new to Rust and have spent most of my time writing code in C/C++.我是 Rust 的新手,大部分时间都在用 C/C++ 编写代码。 I have a flask webserver that returns back a packed data structure in the form of length + null-terminated string:我有一个 Flask 网络服务器,它以长度 + 空终止字符串的形式返回一个打包的数据结构:

test_data = "Hello there bob!" + "\x00"
test_data = test_data.encode("utf-8")
data = struct.pack("<I", len(test_data ))
data += test_data
return data

In my rust code, I'm using the easy_http_request crate and can successfully get the response back by calling get_from_url_str .在我的easy_http_request代码中,我使用的是easy_http_request crate 并且可以通过调用get_from_url_str成功获取响应。 What I'm trying to do is map the returned response back to the Test data structure (if possible).我想要做的是将返回的响应映射回Test数据结构(如果可能)。 I've attempted to use align_to to unsuccessfully get the string data mapped to the structure.我试图使用align_to未能成功地将字符串数据映射到结构。

extern crate easy_http_request;
extern crate libc;

use easy_http_request::DefaultHttpRequest;
use libc::c_char;

#[repr(C, packed)]
#[derive(Debug, Clone, Copy)]
struct Test {
    a: u32,
    b: *const c_char  // TODO: What do I put here???
}

fn main() {
    let response = DefaultHttpRequest::get_from_url_str("http://localhost:5000/").unwrap().send().unwrap();
    let (head, body, _tail) = unsafe { response.body.align_to::<Test>() };
    let my_test: Test = body[0];
    println!("{}", my_test.a); // Correctly prints '17'
    println!("{:?}", my_test.b); // Fails
}

I'm not sure this is possible in Rust.我不确定这在 Rust 中是否可行。 In the response.body I can correctly see the null-terminated string, so I know the data is there.response.body我可以正确地看到以空字符结尾的字符串,所以我知道数据在那里。 Just unsure if there's a way to map it to a string in the Test structure.只是不确定是否有办法将它映射到Test结构中的字符串。 There's no reason I need to use a null-terminated string.我没有理由需要使用以空字符结尾的字符串。 Ultimately, I'm just trying to map a data structure of size and a string to a Rust struct of the similar types.最终,我只是尝试将大小和字符串的数据结构映射到类似类型的 Rust 结构。

It looks like you are confused by two different meanings of pack : * In Python, pack is a protocol of sorts to serialize data into an array of bytes.看起来您对pack的两种不同含义感到困惑: * 在 Python 中, pack是一种将数据序列化为字节数组的协议。 * In Rust, pack is a directive added to a struct to remove padding between members and disable other weirdness. * 在 Rust 中, pack是添加到结构中的指令,用于删除成员之间的填充并禁用其他奇怪的东西。

While they can be use together to make a protocol work, that is not the case, because in your pack you have a variable-length member.虽然它们可以一起使用以使协议起作用,但事实并非如此,因为在您的包中您有一个可变长度的成员。 And trying to serialize/deserialize a pointer value directly is a very bad idea.尝试直接序列化/反序列化指针值是一个非常糟糕的主意。

Your packed flask message is basically:您打包的烧瓶消息基本上是:

  • 4 bytes litte endian value with the number of bytes in the string. 4 个字节的小端值与字符串中的字节数。
  • so many bytes indicated above for the string, encoded in utf-8.上面为字符串指示的这么多字节,以 utf-8 编码。

For that you do not need a packed struct.为此,您不需要打包的结构。 The easiest way is to just read the fields manually, one by one.最简单的方法是手动逐个读取字段。 Something like this (error checking omitted):像这样(省略错误检查):

use std::convert::TryInto;

let a = i32::from_le_bytes(response[0..4].try_into().unwrap());
let b = std::str::from_utf8(&response[4 .. 4 + a as usize]).unwrap();

Don't use raw pointers, they are unsafe to use and recommended only when there are strong reasons to get around Rust's safety guarantees.不要使用原始指针,它们使用起来不安全,只有在有充分理由绕过 Rust 的安全保证时才推荐使用

At minumum a struct that fits your requirement is something like:至少符合您要求的结构类似于:

struct Test<'a> {
    value: &'a str
}

or a String owned value for avoiding lifetime dependencies.String拥有的值,以避免生命周期依赖。

A reference to &str comprises a len and a pointer (it is not a C-like char * pointer).&str的引用包含一个 len 和一个指针(它不是类似 C 的char *指针)。

By the way, the hard part is not the parsing of the wire protocol but to manage correctly all the possible decoding errors and avoid unexpected runtime failures in case of buggy or malicious clients.顺便说一下,困难的部分不是有线协议的解析,而是正确管理所有可能的解码错误,并在出现错误或恶意客户端的情况下避免意外的运行时故障。

In order not to reinvent the wheel, an example with the parse combinator nom :为了不重新发明轮子,使用解析组合器nom的示例:

use nom::{
    number::complete::le_u32,
    bytes::complete::take,
    error::ErrorKind,
    IResult
};
use easy_http_request::DefaultHttpRequest;
use std::str::from_utf8;

#[derive(Debug, Clone)]
struct Test {
    value: String
}

fn decode_len_value(bytes: &[u8]) -> IResult<&[u8], Test> {
    let (buffer, len) = le_u32(bytes)?;

    // take len-1 bytes because null char (\0) is accounted into len
    let (remaining, val) = take(len-1)(buffer)?;
    match from_utf8(val) {
        Ok(strval) => Ok((remaining, Test {value: strval.to_owned()})),
        Err(_) => Err(nom::Err::Error((remaining, ErrorKind::Char)))
    }
}

fn main() {
    let response = DefaultHttpRequest::get_from_url_str("http://localhost:5000/").unwrap().send().unwrap();
    let result = decode_len_value(&response.body);
    println!("{:?}", result);
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM