Parsing dei pacchetti tramite struct

by luca
4 comments

Analizzando il codice sorgente di un programma opensource, ho trovato una interessante tecnica per effettuare il parsing dei dati ricevuti in maniera molto efficente, utilizzando una struct.

Questa tecnica funziona se i dati ricevuti hanno lunghezza e struttura fissa, ad esempio si applica molto bene a pacchetti dati.

In fase di ricezione, normalmente il programma memorizza i dati ricevuti in un buffer all’interno della memoria RAM. Ipotizziamo che il pacchetto abbia una lunghezza di 14 bytes:

char rx_buffer[14];

Le specifiche prevedono che tale pacchetto sia formato dai seguenti campi:

[checklist]

  • indirizzo, 5 bytes
  • valore di temperatura, 3 bytes
  • valore di umidità, 3 bytes
  • contatore di trasmissione (TX), 1 bytes
  • checksum, 2 bytes

[/checklist]

Possiamo definire una struct che rappresenta la struttura del pacchetto:

typedef struct {
  uint8_t address[5];
  uint8_t temperature[3];
  uint8_t humidity[3];
  uint8_t tx;
  uint8_t checksum[2];
} my_packet;

La tecnica consiste nell’applicare, come se fosse un template, la struct al buffer:

mem-struct-001

in tal modo i bytes ricevuti vengono automaticamente suddivisi nei vari campi:

mem-struct-002

Per effettuare il parsing è quindi sufficiente effettuare un cast del buffer:

my_packet* parsed_data = (my_packet*)rx_buffer;

e accedere ai dati come campi di tale struct:

parsed_data->address;
parsed_data->temperature;
[...]

Padding e packed

Abbiamo però un problema: per rendere più efficiente l’accesso alla memoria, i compilatori normalmente allineano le variabili in base alla loro dimensione. Questo significa che – quando viene allocato in memoria spazio per la struct – è possibile che tra le variabili vi sia dello spazio “libero” (chiamato padding):

mem-struct-003

La presenza del padding causa un disallineamento se applichiamo la struct al buffer con i dati ricevuti (che invece sono continui). Fortunatamente è possibile richiedere al compilatore di non inserire padding utilizzando l’attibuto packed:

typedef struct {
  uint8_t address[5];
  uint8_t temperature[3];
  uint8_t humidity[3];
  uint8_t tx;
  uint8_t checksum[2];
}__attribute__((packed)) my_packet;

In questo modo la tecnica mostrata ad inizio articolo funziona perfettamente!

Related Posts

4 comments

whm 18 gennaio 2018 - 09:01

Isn’t parsed_data a pointer? To access the struct field, shouldn’t you use

parsed_data->address

instead of

parsed_data.address

Reply
luca 18 gennaio 2018 - 09:24

you’re absolutely right! thanks

Reply
francesco 18 gennaio 2018 - 15:42

mi sembra una valida alternativa a json

Reply
Federico 5 aprile 2018 - 14:51

Veramente interessante, grazie!

Reply

Leave a Comment

diciannove + 17 =