Imballaggio bit portatile e stretto

Oct 20 2020

Supponiamo che io ho tre unsigned ints, { a, b, c, d, rispettivamente}, che voglio mettere in valigia con lunghezze non standard, {} 9,5,7,11. Vorrei creare un pacchetto di rete ( unsigned char pkt[4]) in cui impacchettare questi valori e decomprimerli in modo affidabile su un'altra macchina utilizzando lo stesso file di intestazione indipendentemente dall'endianness.

Tutto quello che ho letto sull'uso di strutture compresse suggerisce che l'ordinamento dei bit non sarà prevedibile, quindi è fuori discussione. Quindi questo mi lascia con operazioni bit-set e bit-clear, ma non sono sicuro di come garantire che endianness non mi causi problemi. Quanto segue è sufficiente o dovrò incontrare problemi con l'endianness di ae dseparatamente?

void pack_pkt(uint16_t a, uint8_t b, uint8_t c, uint16_t d, uint8_t *pkt){
    uint32_t pkt_h = ((uint32_t)a & 0x1FF)      // 9 bits
                 | (((uint32_t)b & 0x1F) << 9)  // 5 bits
                 | (((uint32_t)c & 0x3F) << 14) // 7 bits
                 | (((uint32_t)d & 0x7FF) << 21); //11 bits
    *pkt = htonl(pkt_h);
}

void unpack_pkt(uint16_t *a, uint8_t *b, uint8_t *c, uint16_t *d, uint8_t *pkt){
    uint32_t pkt_h = ntohl(*pkt);
    (*a) = pkt_h & 0x1FF;
    (*b) = (pkt_h >> 9) & 0x1F;
    (*c) = (pkt_h >> 14) & 0x3F;
    (*d) = (pkt_h >> 21) & 0x7FF;
}

In caso affermativo, quali altre misure posso adottare per garantire la portabilità?

Risposte

1 harold Oct 20 2020 at 19:49

Le strutture con campi di bit sono effettivamente essenzialmente inutili per questo scopo, poiché il loro ordine dei campi e persino le regole di riempimento non sono coerenti.

dovrò incontrare problemi con l'endianness di ae dseparatamente?

L'endianness di ae dnon importa, il loro byte-order non viene mai utilizzato. ae dnon vengono reinterpretati come byte grezzi, vengono utilizzati o assegnati solo i loro valori interi e in quei casi endianness non entra nell'immagine.

C'è un altro problema però: uint8_t *pktin combinazione con *pkt = htonl(pkt_h);significa che viene salvato solo il byte meno significativo (indipendentemente dal fatto che venga eseguito da una macchina little endian o big endian, perché questa non è una reinterpretazione, è una conversione implicita). uint8_t *pktè OK da solo, ma poi il gruppo risultante di 4 byte deve essere copiato nel buffer a cui punta, non può essere assegnato tutto in una volta. uint32_t *pktconsentirebbe a tale assegnazione singola di funzionare senza perdere dati, ma ciò rende la funzione meno comoda da usare.

Allo stesso modo unpack_pkt, attualmente viene utilizzato solo un byte di dati.

Quando questi problemi vengono risolti, dovrebbe essere buono:

void pack_pkt(uint16_t a, uint8_t b, uint8_t c, uint16_t d, uint8_t *buffer){
    uint32_t pkt_h = ((uint32_t)a & 0x1FF)      // 9 bits
                 | (((uint32_t)b & 0x1F) << 9)  // 5 bits
                 | (((uint32_t)c & 0x3F) << 14) // 7 bits
                 | (((uint32_t)d & 0x7FF) << 21); //11 bits
    uint32_t pkt = htonl(pkt_h);
    memcpy(buffer, &pkt, sizeof(uint32_t));
}

void unpack_pkt(uint16_t *a, uint8_t *b, uint8_t *c, uint16_t *d, uint8_t *buffer){
    uint32_t pkt;
    memcpy(&pkt, buffer, sizeof(uint32_t));
    uint32_t pkt_h = ntohl(pkt);
    (*a) = pkt_h & 0x1FF;
    (*b) = (pkt_h >> 9) & 0x1F;
    (*c) = (pkt_h >> 14) & 0x3F;
    (*d) = (pkt_h >> 21) & 0x7FF;
}

Un'alternativa che funziona senza preoccuparsi dell'endianness in qualsiasi momento è la decostruzione manuale del uint32_t(invece di scambiarlo in modo condizionale con byte htonle quindi reinterpretarlo come byte grezzi), ad esempio:

void pack_pkt(uint16_t a, uint8_t b, uint8_t c, uint16_t d, uint8_t *pkt){
    uint32_t pkt_h = ((uint32_t)a & 0x1FF)      // 9 bits
                 | (((uint32_t)b & 0x1F) << 9)  // 5 bits
                 | (((uint32_t)c & 0x3F) << 14) // 7 bits
                 | (((uint32_t)d & 0x7FF) << 21); //11 bits
    // example serializing the bytes in big endian order, regardless of host endianness
    pkt[0] = pkt_h >> 24;
    pkt[1] = pkt_h >> 16;
    pkt[2] = pkt_h >> 8;
    pkt[3] = pkt_h;
}

L'approccio originale non è male, questa è solo un'alternativa, qualcosa da considerare. Poiché nulla viene mai reinterpretato, l'endianness non ha alcuna importanza, il che può aumentare la fiducia nella correttezza del codice. Ovviamente come svantaggio, richiede più codice per ottenere la stessa cosa. A proposito, anche se decostruire manualmente uint32_te memorizzare 4 byte separati sembra un sacco di lavoro, GCC può compilarlo in modo efficiente in un bswaparchivio a 32 bit. D'altra parte Clang perde questa opportunità e potrebbero farlo anche altri compilatori, quindi questo non è privo di inconvenienti.

AliMirghasemi Oct 20 2020 at 05:36

per il confezionamento e il confezionamento suggerisco di usare struct come questo

  • ricorda che la dimensione della struttura è diversa in altre macchine come il sistema a 8 bit rispetto al sistema a 32 bit compila la stessa struttura con dimensioni diverse la chiamiamo padding in struct così puoi usare pack per assicurarti che la dimensione della struttura sia la stessa in trasmettitore e ricevitore
typedef struct {
    uint8_t A;
    uint8_t B;
    uint8_t C;
    uint8_t D;
} MyPacket;

ora puoi trasmettere questa struttura in un flusso di byte come SerialPort o UART o qualcos'altro e nel ricevitore puoi impacchettare i byte insieme

vedere le seguenti funzioni

void transmitPacket(MyPacket* packet) {
    int len = sizeof(MyPacket);
    uint8_t* pData = (uint8_t*) packet;
    while (len-- > 0) {
        // send bytes 1 by 1
        transmitByte(*pData++);
    }
}

void receivePacket(MyPacket* packet) {
    int len = sizeof(MyPacket);
    uint8_t* pData = (uint8_t*) packet;
    while (len-- > 0) {
        // receive bytes 1 by 1
        *pData++ = receiveByte();
    }
}

ricorda che l'ordinamento dei bit in byte è lo stesso ovunque ma devi controllare l'ordine dei byte per assicurarti che il pacchetto non venga perso nel ricevitore

per esempio, se la dimensione del pacchetto è di 4 byte e si invia prima un byte basso, è necessario ricevere un byte basso nel ricevitore

nel tuo codice ottieni il pacchetto nel puntatore uint8_t * ma la dimensione effettiva del pacchetto è uint32_t ed è di 4 byte