NanoPB : Comment gérer les types bytes en C

English Français

Voir aussi : Version C++ : Comment gérer les types bytes en C++

NanoPB est une implémentation de Protocol Buffers optimisée pour la taille du code, destinée aux systèmes embarqués. Cet article montre comment gérer les types bytes en C avec NanoPB.

Définition proto

Créez d’abord un fichier .proto avec des champs bytes :

bytes.proto
syntax = "proto3";

package example;

message BytesMessage {
  bytes data = 1;
  bytes signature = 2;
}

Générer le code NanoPB

Générez le code NanoPB avec un fichier .options pour spécifier les tailles de tampon bytes :

Créez bytes.options :

bytes.options
example.BytesMessage.data max_size:32
example.BytesMessage.signature max_size:16

Puis générez :

generate_nanopb_bytes.sh
protoc --nanopb_out=. bytes.proto

Cela générera bytes.pb.h et bytes.pb.c.

Exemple en C avec tampons de taille fixe

Voici un exemple complet en C utilisant des tampons de taille fixe :

bytes_example.c
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include "bytes.pb.h"
#include "pb_encode.h"
#include "pb_decode.h"

int main() {
    // Tampon pour le message encodé
    uint8_t buffer[256];
    size_t message_length;
    
    // --- ENCODAGE ---
    example_BytesMessage message = example_BytesMessage_init_zero;
    
    // Définir les données bytes
    const uint8_t data[] = {0x01, 0x02, 0x03, 0x04, 0x05};
    const uint8_t signature[] = {0xAA, 0xBB, 0xCC, 0xDD};
    
    message.data.size = sizeof(data);
    memcpy(message.data.bytes, data, sizeof(data));
    
    message.signature.size = sizeof(signature);
    memcpy(message.signature.bytes, signature, sizeof(signature));
    
    // Créer un flux pour l'encodage
    pb_ostream_t ostream = pb_ostream_from_buffer(buffer, sizeof(buffer));
    
    // Encoder le message
    if (!pb_encode(&ostream, example_BytesMessage_fields, &message)) {
        printf("Encoding failed: %s\n", PB_GET_ERROR(&ostream));
        return 1;
    }
    
    message_length = ostream.bytes_written;
    printf("Encoded %zu bytes\n", message_length);
    
    // Afficher le dump hexadécimal des données encodées
    printf("Encoded data: ");
    for (size_t i = 0; i < message_length; i++) {
        printf("%02x ", buffer[i]);
    }
    printf("\n");
    
    // --- DÉCODAGE ---
    example_BytesMessage decoded = example_BytesMessage_init_zero;
    
    // Créer un flux pour le décodage
    pb_istream_t istream = pb_istream_from_buffer(buffer, message_length);
    
    // Décoder le message
    if (!pb_decode(&istream, example_BytesMessage_fields, &decoded)) {
        printf("Decoding failed: %s\n", PB_GET_ERROR(&istream));
        return 1;
    }
    
    // Afficher les valeurs décodées
    printf("Decoded values:\n");
    printf("  data: ");
    for (size_t i = 0; i < decoded.data.size; i++) {
        printf("%02x ", decoded.data.bytes[i]);
    }
    printf("\n");
    printf("  signature: ");
    for (size_t i = 0; i < decoded.signature.size; i++) {
        printf("%02x ", decoded.signature.bytes[i]);
    }
    printf("\n");
    
    return 0;
}

Commande de compilation

Compilez l’exemple avec nanopb. NanoPB est généralement utilisé en incluant directement les fichiers sources dans votre projet :

compile_bytes_example.sh
gcc -o bytes_example bytes_example.c bytes.pb.c pb_common.c pb_encode.c pb_decode.c -I.

Remarque : Les fichiers sources NanoPB (pb_common.c, pb_encode.c, pb_decode.c) doivent être compilés directement avec votre projet. Vous pouvez les obtenir depuis le dépôt GitHub NanoPB.

Script de test Python

Pour vérifier l’encodage, vous pouvez utiliser la bibliothèque protobuf de Python :

test_bytes.py
import bytes_pb2

# Lire les données binaires
with open('encoded.bin', 'rb') as f:
    data = f.read()

# Décoder
msg = bytes_pb2.BytesMessage()
msg.ParseFromString(data)

print("Python decoded values:")
print(f"  data: {msg.data.hex()}")
print(f"  signature: {msg.signature.hex()}")

Compilez d’abord les définitions protobuf Python :

compile_python_bytes.sh
protoc --python_out=. bytes.proto

Modifiez ensuite l’exemple C pour enregistrer les données encodées dans un fichier :

save_encoded_bytes.c
// Après l'encodage, ajoutez ceci :
FILE *f = fopen("encoded.bin", "wb");
fwrite(buffer, 1, message_length, f);
fclose(f);

Alternative : bytes basé sur des callbacks

Pour une gestion dynamique des bytes, vous pouvez utiliser des callbacks. Créez bytes_callback.options :

bytes_callback.options
# Utiliser un callback pour les bytes dynamiques
msg.BytesMessage.data callback
msg.BytesMessage.signature callback

Régénérez ensuite et utilisez cette approche :

bytes_callback_example.c
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include "bytes.pb.h"
#include "pb_encode.h"
#include "pb_decode.h"

typedef struct {
    uint8_t* data;
    size_t size;
} dynamic_bytes_t;

// Callback d'encodage pour les bytes
bool bytes_encode_callback(pb_ostream_t *stream, const pb_field_t *field, void * const *arg) {
    const dynamic_bytes_t* bytes = (const dynamic_bytes_t*)*arg;
    
    if (!pb_encode_tag_for_field(stream, field))
        return false;
    
    return pb_encode_string(stream, bytes->data, bytes->size);
}

// Callback de décodage pour les bytes
bool bytes_decode_callback(pb_istream_t *stream, const pb_field_t *field, void **arg) {
    dynamic_bytes_t* bytes = (dynamic_bytes_t*)*arg;
    
    // Allouer le tampon
    size_t size = stream->bytes_left;
    bytes->data = (uint8_t*)malloc(size);
    
    if (!bytes->data) {
        return false;
    }
    
    bytes->size = size;
    
    // Lire les données dans le tampon
    if (!pb_read(stream, bytes->data, size)) {
        free(bytes->data);
        bytes->data = NULL;
        bytes->size = 0;
        return false;
    }
    
    return true;
}

int main() {
    uint8_t buffer[256];
    size_t message_length;
    
    uint8_t data_array[] = {0x01, 0x02, 0x03, 0x04, 0x05};
    uint8_t signature_array[] = {0xAA, 0xBB, 0xCC, 0xDD};
    
    dynamic_bytes_t data = {data_array, sizeof(data_array)};
    dynamic_bytes_t signature = {signature_array, sizeof(signature_array)};
    
    // --- ENCODAGE ---
    example_BytesMessage message = example_BytesMessage_init_zero;
    
    message.data.funcs.encode = bytes_encode_callback;
    message.data.arg = &data;
    message.signature.funcs.encode = bytes_encode_callback;
    message.signature.arg = &signature;
    
    pb_ostream_t ostream = pb_ostream_from_buffer(buffer, sizeof(buffer));
    
    if (!pb_encode(&ostream, example_BytesMessage_fields, &message)) {
        printf("Encoding failed: %s\n", PB_GET_ERROR(&ostream));
        return 1;
    }
    
    message_length = ostream.bytes_written;
    printf("Encoded %zu bytes\n", message_length);
    
    // --- DÉCODAGE ---
    example_BytesMessage decoded = example_BytesMessage_init_zero;
    dynamic_bytes_t decoded_data = {NULL, 0};
    dynamic_bytes_t decoded_signature = {NULL, 0};
    
    decoded.data.funcs.decode = bytes_decode_callback;
    decoded.data.arg = &decoded_data;
    decoded.signature.funcs.decode = bytes_decode_callback;
    decoded.signature.arg = &decoded_signature;
    
    pb_istream_t istream = pb_istream_from_buffer(buffer, message_length);
    
    if (!pb_decode(&istream, example_BytesMessage_fields, &decoded)) {
        printf("Decoding failed: %s\n", PB_GET_ERROR(&istream));
        return 1;
    }
    
    printf("Decoded values:\n");
    printf("  data: ");
    for (size_t i = 0; i < decoded_data.size; i++) {
        printf("%02x ", decoded_data.data[i]);
    }
    printf("\n");
    printf("  signature: ");
    for (size_t i = 0; i < decoded_signature.size; i++) {
        printf("%02x ", decoded_signature.data[i]);
    }
    printf("\n");
    
    // Libérer la mémoire allouée
    free(decoded_data.data);
    free(decoded_signature.data);
    
    return 0;
}

Points clés

Quand utiliser quelle approche

Sortie attendue

bytes_expected_output.txt
Encoded 14 bytes
Encoded data: 0a 05 01 02 03 04 05 12 04 aa bb cc dd 
Decoded values:
  data: 01 02 03 04 05 
  signature: aa bb cc dd 

Différences par rapport au C++

La version C est presque identique à la version C++, avec des différences clés :

Cas d’usage des bytes

Plus d’articles sur NanoPB


Check out similar posts by category: Embedded, C/C++, Protocol Buffers