Rust - serde/bincode serializacia/deserializacia dat

Rust - serde/bincode serializacia/deserializacia dat
« kdy: 17. 12. 2021, 22:34:04 »
Ahojte kolegovia,

Tentokrat sa na Vas obraciam kvoli serializacii/deserializacii struktur(y) - jednoduchych MSG (TCP/IP) medzi serverom(Rust) a klientom C/freeRTOS(32bit MCU, 128kb RAM).
Spravy su vsehovsudy jednoduche, nieje nutne ich balit do msgpack, protobuf, bson a pod.

Na serializaciu/deserializaciu som pouzil serde/bincode, s tym ze vysledna sprava sa sklada z MessageHeader + MessageBody => Message. Na tomto designe sa mi nepacia dve veci, a to:

MessageHeader::message_type je ulozena hodnota z MessageBodyType::{Version, BatteryHealth} a sucastne Message::body obsahuje {enum MessageBody::Version(Version), enum BatteryHealth(BatteryHealth) }
co ma za nasledok ze Serde serializuje Message(Version) nasledovne

Kód: [Vybrat]
0             1       2      3        4
[message_type | _pad0 | body_size     ]
[              crc                    ]
[              enum                   ]      <---- serde vlozil enum
[x1           |  x2   |     x3        ]

Takze "struct MessageHeader" ma dlzku 8 bajtov, "struct Version" ma dlzku 4 bajty => idealne by "struct Message" mal mat 12 bajtov.
V skutocnosti ma "struct Message" 16 bajtov, a to z dovodu ze serde prida 4 bajty na rozlisenie enum MessageBody::{Version, BatteryHealth}

Serializovany objekt s prihladnutim na kod vyzsie vyzera nasledovne, kde 11/22 je MessageBodyType::{Version, BatteryHealth}, a 0/1 na 9 pozicii je "enum MessageBody::{Version(), BatteryHealth()}"
Kód: [Vybrat]
serialized_version: (16) vec![11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ...]
serialized_battery: (16) vec![22, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, ...]

 
Preto sa pytam skusenejsich, akceptovat to, ze serde prida 4bajty na rozlisenie enumu a tuto vec zohladit na strane klienta,
Pripadne, napada Vas moznost ako namodelovat kod nizsie, tak, aby serde/bincode serializovalo/deserializovalo iba mne chcenych 12bajtov (MessageHeader + MessageBody)

AD: Pokusal som sa namodelovat "pub fn deserialize<'a>(bytes: &'a [u8]) -> Box<MessageTrait>", toto sa mi nepodarilo skrz (https://doc.rust-lang.org/error-index.html#E0038)

Rust:
    cargo - serde
    cargo - bincode
Kód: [Vybrat]
use serde_repr::*;
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize, PartialEq, Debug)]
pub enum MessageBody {
    Version(Version),
    BatteryHealth(BatteryHealth),
}

#[derive(Serialize_repr, Deserialize_repr, PartialEq, Debug)]
#[repr(u8)]
pub enum MessageBodyType {
    Version = 11,
    BatteryHealth = 22,
}

impl MessageBody {
    pub fn value(&self) -> MessageBodyType {
        match *self {
            MessageBody::Version(_) => MessageBodyType::Version,
            MessageBody::BatteryHealth(_) => MessageBodyType::BatteryHealth
        }
    }
}

#[derive(Serialize, Deserialize, PartialEq, Debug)]
#[repr(C)]
pub struct MessageHeader {
    message_type: MessageBodyType,  // 1
    _pad0: u8,                      // 1
    body_size: u16,                 // 2
    crc: u32,                       // 4
}

impl MessageHeader {
  pub fn new_as(message_type: MessageBodyType) -> MessageHeader {
    MessageHeader {
        message_type: message_type,
        _pad0: 0,
        body_size: 0,
        crc: 0,
    }
  }
}

#[derive(Serialize, Deserialize, PartialEq, Debug)]
struct Message {
    header: MessageHeader,
    body: MessageBody,
}
impl Message {
    pub fn serialize(mt: MessageBody) -> Vec<u8> {
        let header = MessageHeader::new_as(mt.value());
        let header_size_of = std::mem::size_of::<MessageHeader>();
        let header_vec =  bincode::serialize(&header).unwrap();
        let header_vec_len = header_vec.len();


        let body_version_size_of = std::mem::size_of::<Version>();
        let body_vec: Vec<u8> = bincode::serialize(&mt).unwrap();
        let body_vec_vec_len = body_vec.len();


        let message: Message = Message { header:  header, body: mt };
        let message_size_of = std::mem::size_of::<Message>();
        let message_vec = bincode::serialize(&message).unwrap();
        let message_len = message_vec.len();
       
        message_vec
    }

    pub fn deserialize<'a>(bytes: &'a [u8]) -> Message {
        let message_header_len = std::mem::size_of::<MessageHeader>();

        let header_slice: &[u8] = &bytes[0..message_header_len];
        let header: MessageHeader = bincode::deserialize(header_slice).unwrap();

        let message: Message = bincode::deserialize(bytes).unwrap();

        message
    }
}

#[derive(Serialize, Deserialize, PartialEq, Debug)]
#[repr(C)]
pub struct Version {
    pub x1: u8,       // 1
    pub x2: u8,       // 1
    pub x3: u16,      // 2
}

#[derive(Serialize, Deserialize, PartialEq, Debug)]
#[repr(C)]
pub struct BatteryHealth {
    pub x1: u8,       // 1
    pub x2: u8,       // 1
    pub x3: u16,      // 2
}

fn main () -> Result<(), Box<(dyn std::error::Error + 'static)>> {
    let serialized_version: Vec<u8> = Message::serialize(MessageBody::Version(Version{x1: 3, x2: 6, x3: 0xFAFA}));
    let serialized_battery: Vec<u8> = Message::serialize(MessageBody::BatteryHealth(BatteryHealth{x1: 10, x2: 20, x3: 0x0A0A}));

    let deserialized_version: Message = Message::deserialize(&serialized_version);


  Ok(())
}

Diky M.


RDa

  • *****
  • 1 641
    • Zobrazit profil
    • E-mail
Re:Rust - serde/bincode serializacia/deserializacia dat
« Odpověď #1 kdy: 17. 12. 2021, 23:07:50 »
Dekuji za potvrzeni, ze rust neni neco co by melo hardcore/lowlevel C programatory zajimat :)

Tolik s**ni s tim, a jeste si to dela co chce, a ne co chci ja mi za to fakt nestoji.

Re:Rust - serde/bincode serializacia/deserializacia dat
« Odpověď #2 kdy: 18. 12. 2021, 00:22:05 »
Možná pro to bude nějaký dobrý důvod, ale ten kód mi přijde příliš komplikovaný. MessageBodyType je již informace obsažená v MessageBody. Pokud se zbavíme MessageBodyType, zbývá snad už jen jeden problém, a to, že ten tag zabírá 4B a ne jen 1B. IIUC, to by mělo jít nastavit vhodným nastavením serde: https://stackoverflow.com/a/64512528

Drawback samozřejmě bude zpětná nekompatibilita, pokud by ten enum měl přes 256 variant. To plyne ze způsobu kódování. Pak by se jeho tag nevlezl do u8, a byla by to komplikace. Samozřejmě by to šlo řešit ála UTF-8, mít 256 variant enumu, z toho jedna bude Other, která bude kódovat ty zbývající. Hlavní by ale bylo si na to vzpomenout při přidání 256. varianty. Pokud si na to vzpomenete u 257. varianty, bude už z hlediska kompatibility pozdě.

Re:Rust - serde/bincode serializacia/deserializacia dat
« Odpověď #3 kdy: 18. 12. 2021, 00:49:50 »
Aha, tak s tím řešením délky tagu si nejsem jistý, to možná ořeže ten enum do podoby bez dalších dat. Ale nevím, až tak sběhlý v tom nejsem.

Ink

  • ****
  • 450
    • Zobrazit profil
    • E-mail
Re:Rust - serde/bincode serializacia/deserializacia dat
« Odpověď #4 kdy: 18. 12. 2021, 10:11:45 »
Dekuji za potvrzeni, ze rust neni neco co by melo hardcore/lowlevel C programatory zajimat :)

Tolik s**ni s tim, a jeste si to dela co chce, a ne co chci ja mi za to fakt nestoji.

Ukaž, jak bys to udělal v C a že v Rustu to nejde. Rust má i normální "hloupý" union (untagged). OP použil tagged union a vadí mu, že vysokoúrovňový serializační mechanismus ho zachová v pořádku se vším všudy. Ale jinak jasně, pokud Ti to za to nestojí, nezabývej se tím.


RDa

  • *****
  • 1 641
    • Zobrazit profil
    • E-mail
Re:Rust - serde/bincode serializacia/deserializacia dat
« Odpověď #5 kdy: 18. 12. 2021, 10:33:14 »
Dekuji za potvrzeni, ze rust neni neco co by melo hardcore/lowlevel C programatory zajimat :)

Tolik s**ni s tim, a jeste si to dela co chce, a ne co chci ja mi za to fakt nestoji.

Ukaž, jak bys to udělal v C a že v Rustu to nejde. Rust má i normální "hloupý" union (untagged). OP použil tagged union a vadí mu, že vysokoúrovňový serializační mechanismus ho zachová v pořádku se vším všudy. Ale jinak jasně, pokud Ti to za to nestojí, nezabývej se tím.

Jako co na tom chces probuh resit?

typedef struct / union, + __packed__ atribut, pro exaktni definici zpravy
(uint8_t*)&message, pro pretypovani na byte array a sup s tim nekam, pomoci sizeof(message)

Dnesni programatori fakt nemaji tuseni o fyzickem ukladani struktur? Jako taky me prekvapilo u borce co masti Xcode a apple appky, ze nedokazal nastavit bitovy flag popsany v specifikaci - mezi osmy bajty, pomoci jednoducheho msg[0] |= 0x40.

Re:Rust - serde/bincode serializacia/deserializacia dat
« Odpověď #6 kdy: 18. 12. 2021, 11:10:04 »
Je bincode vůbec pro C? Slyšel jsem že se možná něco plánuje v C++, ale to je tak vše.

Re:Rust - serde/bincode serializacia/deserializacia dat
« Odpověď #7 kdy: 18. 12. 2021, 11:29:41 »
Bych na to možná reagoval, ale úplně nechci z diskuze o řešení serializace v Rustu dělat vlákno Rust vs. C.

Idris

  • *****
  • 1 786
    • Zobrazit profil
    • E-mail
Re:Rust - serde/bincode serializacia/deserializacia dat
« Odpověď #8 kdy: 18. 12. 2021, 11:45:48 »
Bych na to možná reagoval, ale úplně nechci z diskuze o řešení serializace v Rustu dělat vlákno Rust vs. C.
To je tu celkem normální a v tomto případě asi i přínosné.

Re:Rust - serde/bincode serializacia/deserializacia dat
« Odpověď #9 kdy: 18. 12. 2021, 12:35:47 »
Normální neznamená, že je to dobře. Ale OK, tady je to takové hraniční. Vidím trochu potenciál k flamewar.

Ono by se něco podobného jako v C dalo udělat i v Rustu. Je to ale za cenu raw unionů, transmute a unsafe. Pak se na libovolnou strukturu lze dívat jako na [u8]. Tím ale v podstatě z Rustu stává tak trochu C, a přináší to nevýhody s tím spojené:

* Výstup bude fixed-length.
* Pokud budou různé varianty unionu různě dlouhé, je tu asi riziko neinicializované paměti a úniku dat.
* Nepůjde použít pokročilejší (de)serializaci. V případě referencí výsledek asi nebude žádoucí…
* Little vs. big endian
* Nic nekontroluje validitu dat; pokud enum má dvě platné hodnoty, může dostat něco úplně jiného. Toto zavání nedefinovaným chováním.

Jinými slovy, i v Rustu to můžete udělat jako v Céčku, ale pak to máte jako v Céčku se vším všudy.

Re:Rust - serde/bincode serializacia/deserializacia dat
« Odpověď #10 kdy: 18. 12. 2021, 12:59:24 »
Ano, i když se dá Rust použít na stejné věci jako C, neznamená to že je to »vylepšené a bezpečnější C«. Člověk musí změnit svůj způsob myšlení.

Idris

  • *****
  • 1 786
    • Zobrazit profil
    • E-mail
Re:Rust - serde/bincode serializacia/deserializacia dat
« Odpověď #11 kdy: 18. 12. 2021, 13:46:48 »
Normální neznamená, že je to dobře. Ale OK, tady je to takové hraniční. Vidím trochu potenciál k flamewar.

Ono by se něco podobného jako v C dalo udělat i v Rustu. Je to ale za cenu raw unionů, transmute a unsafe. Pak se na libovolnou strukturu lze dívat jako na [u8]. Tím ale v podstatě z Rustu stává tak trochu C, a přináší to nevýhody s tím spojené:

* Výstup bude fixed-length.
* Pokud budou různé varianty unionu různě dlouhé, je tu asi riziko neinicializované paměti a úniku dat.
* Nepůjde použít pokročilejší (de)serializaci. V případě referencí výsledek asi nebude žádoucí…
* Little vs. big endian
* Nic nekontroluje validitu dat; pokud enum má dvě platné hodnoty, může dostat něco úplně jiného. Toto zavání nedefinovaným chováním.

Jinými slovy, i v Rustu to můžete udělat jako v Céčku, ale pak to máte jako v Céčku se vším všudy.
Když je nutné použít transmute a unsafe, je lepší sáhnout přímo po C.

gleng

Re:Rust - serde/bincode serializacia/deserializacia dat
« Odpověď #12 kdy: 18. 12. 2021, 14:20:14 »
ja by som nevysmyslal koleso a pouzil flatbuffers.

Re:Rust - serde/bincode serializacia/deserializacia dat
« Odpověď #13 kdy: 18. 12. 2021, 14:25:40 »
Ukaž, jak bys to udělal v C a že v Rustu to nejde. Rust má i normální "hloupý" union (untagged). OP použil tagged union a vadí mu, že vysokoúrovňový serializační mechanismus ho zachová v pořádku se vším všudy. Ale jinak jasně, pokud Ti to za to nestojí, nezabývej se tím.

OP to nevadi, OP poukazal na fakt ze to tak je. OP by rad vyuzil plne jazyk Rust (kludne aj nizkourovnovu serializaciu (ak mas predstavu ako)) a zaroven sa vyhol unsafe {}.

Vies mi poradit ako serializovat struktury (kludne aj bez serde/bincode), s tym ze vyuzijem plne jazyk Rust "se vsim vsudy", vyhnem sa "unsafe" semantike?

Rad by som sa vyhol flamewar C vs Rust, vsetci tusime ako to zapiseme v C, ako to spravit Safe v Ruste?

Idris

  • *****
  • 1 786
    • Zobrazit profil
    • E-mail
Re:Rust - serde/bincode serializacia/deserializacia dat
« Odpověď #14 kdy: 18. 12. 2021, 14:41:07 »
[ako serializovat struktury (kludne aj bez serde/bincode), s tym ze vyuzijem plne jazyk Rust "se vsim vsudy", vyhnem sa "unsafe" semantike?
Není nejlepší to v tomto případě napsat natvrdo, jeden bajt na typ a pak řešit switchem, co se načte/vytvoří?