Отобразить C -подобную структуру упакованных данных в структуру Rust - PullRequest
0 голосов
/ 27 февраля 2020

Я довольно новичок в Rust и потратил большую часть своего времени на написание кода на C / C ++. У меня есть 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

В моем коде ржавчины я использую ящик easy_http_request и могу успешно получить ответ, позвонив по номеру get_from_url_str. Я пытаюсь сопоставить возвращенный ответ со структурой данных Test (если это возможно). Я пытался использовать 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
}

Я не уверен, что это возможно в Rust. В response.body я могу правильно видеть строку с нулевым символом в конце, поэтому я знаю, что данные там Просто не уверен, есть ли способ отобразить его на строку в структуре Test. Нет причин, по которым мне нужно использовать строку с нулевым символом в конце. В конечном счете, я просто пытаюсь отобразить структуру данных размера и строки в структуру Rust схожих типов.

Ответы [ 2 ]

1 голос
/ 27 февраля 2020

Похоже, вас смущают два разных значения pack: * В Python, pack - это своего рода протокол для сериализации данных в массив байтов. * В Rust pack - это директива, добавленная в структуру для удаления отступов между членами и отключения других странностей.

Хотя их можно использовать вместе для обеспечения работы протокола, это не так, потому что в ваш пакет у вас есть член переменной длины. И пытаться сериализовать / десериализовать значение указателя напрямую - очень плохая идея.

Ваше упакованное flask сообщение в основном:

  • 4 байта Litte endian значение с количеством байтов в строке.
  • столько байтов, указанных выше для строки, закодированной в utf-8.

Для этого вам не нужна упакованная структура. Самый простой способ - просто прочитать поля вручную, одно за другим. Примерно так (проверка ошибок опущена):

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();
0 голосов
/ 28 февраля 2020

Не используйте необработанные указатели, они небезопасны для использования и рекомендуются только тогда, когда есть веские причины обойти гарантии безопасности Rust.

При минимальной структуре, соответствующей вашим требованиям это что-то вроде:

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

или String значение для избежания зависимости от времени жизни.

Ссылка на &str содержит len и указатель (это не C -подобный * указатель char *).

Кстати, сложная часть состоит не в разборе проводного протокола, а в правильном управлении всеми возможными ошибками декодирования и предотвращением непредвиденных сбоев во время выполнения в случае глючного или злонамеренного клиенты.

Чтобы не изобретать велосипед, пример с комбинатором разбора 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);
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...