Skip to content

Instantly share code, notes, and snippets.

@vpetrigo
Created August 22, 2019 09:57
Show Gist options
  • Save vpetrigo/df85bbb8197e3be76f644bb8290444c4 to your computer and use it in GitHub Desktop.
Save vpetrigo/df85bbb8197e3be76f644bb8290444c4 to your computer and use it in GitHub Desktop.
Pack repeated fields with Nanopb library
#include <pb_decode.h>
#include <pb_encode.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include "Test.pb.h"
bool str_encode(pb_ostream_t *stream, const pb_field_t *field,
void *const *arg) {
const char *str = *arg;
if (!pb_encode_tag_for_field(stream, field)) {
return false;
}
return pb_encode_string(stream, (unsigned char *)str, strlen(str));
}
struct repeated {
void **repeated_f;
int index;
size_t max_size;
};
bool req_encode(pb_ostream_t *stream, const pb_field_t *field,
void *const *arg) {
struct repeated *req = *arg;
while (req->index < req->max_size) {
SearchRequest *sreq = ((SearchRequest **)req->repeated_f)[req->index];
printf("%s: Try to encode: %s\n", __func__, sreq->query.arg);
++req->index;
if (!pb_encode_tag(stream, PB_WT_STRING, field->tag)) {
return false;
}
if (!pb_encode_submessage(stream, SearchRequest_fields, sreq)) {
return false;
}
}
return true;
}
int main(void) {
const char *str1 = "Hello";
const char *str2 = "World";
unsigned char buffer[128];
size_t message_length;
bool status;
Result result_msg = Result_init_default;
pb_ostream_t stream = pb_ostream_from_buffer(buffer, sizeof buffer);
SearchRequest first = {
.query = {.funcs = {.encode = str_encode}, .arg = str1}};
SearchRequest second = {
.query = {.funcs = {.encode = str_encode}, .arg = str2}};
SearchRequest *requests[] = {&first, &second};
struct repeated r1 = {.repeated_f = requests, .index = 0, .max_size = 2};
result_msg.requests.funcs.encode = req_encode;
result_msg.requests.arg = &r1;
status = pb_encode(&stream, Result_fields, &result_msg);
message_length = stream.bytes_written;
if (!status) {
printf("Encoding failed: %s\n", PB_GET_ERROR(&stream));
return -1;
}
printf("Success: %zu written\n", message_length);
for (size_t i = 0; i < message_length; ++i) {
printf("%02hhx", buffer[i]);
}
puts("");
return 0;
}
syntax = "proto3";
message SearchRequest {
string query = 1;
}
message Result {
repeated SearchRequest requests = 1;
}
@ssevans87
Copy link

ssevans87 commented Oct 4, 2021 via email

@vpetrigo
Copy link
Author

vpetrigo commented Oct 4, 2021

@ssevans87, sorry misunderstood your question at first. Here is the snippet:
Repeated.proto

syntax = "proto3";

message Sub {
	int32 num = 1;
	int32 num3 = 2;
}

message Top {
	repeated Sub fields = 1;
}

• dummy app:

// repeated_proto.cpp : Defines the entry point for the application.
//

#include "Repeated.pb.h"

#include <pb_encode.h>
#include <pb_decode.h>

#include <stdlib.h>
#include <stdio.h>

static int value = 10;

static bool fields_encode(pb_ostream_t* stream, const pb_field_t* field, void* const* arg)
{
	Sub message = Sub_init_default;

	for (size_t i = 0; i < 15; ++i) {
		message.num = value;
		message.num2 = value / 10;
		++value;

	    if (!pb_encode_tag_for_field(stream, field)) {
			return false;
		}

		if (!pb_encode_submessage(stream, Sub_fields, &message)) {
			return false;
		}
	}

	return true;
}

bool fields_decode(pb_istream_t* stream, const pb_field_t* field, void** arg)
{
	Sub sub = Sub_init_default;

	if (!pb_decode(stream, Sub_fields, &sub)) {
		return false;
	}

	printf("Sub - num: %d, num2: %d\r\n", sub.num, sub.num2);

	return true;
}

int main(void) {
	unsigned char encode_buf[256];

	Top message = Top_init_default;
	message.fields.funcs.encode = fields_encode;
	pb_ostream_t ostream = pb_ostream_from_buffer(encode_buf, sizeof encode_buf);
	bool status_en = pb_encode(&ostream, Top_fields, &message);

	printf("Encode status: %s, bytes written: %llu\r\n", status_en ? "OK" : "ERR", ostream.bytes_written);

	for (size_t i = 0; i < ostream.bytes_written; ++i) {
		printf("%02hhx", encode_buf[i]);
	}

	puts("");

	pb_istream_t istream = pb_istream_from_buffer(encode_buf, ostream.bytes_written);
	Top message_dec = Top_init_default;
	message_dec.fields.funcs.decode = fields_decode;

	bool status_dec = pb_decode(&istream, Top_fields, &message_dec);

	printf("Decode status: %s, bytes left: %llu\r\n", status_dec ? "OK" : "ERR", istream.bytes_left);

	return 0;
}

So, for submessages you have the following steps:
• encode tag with pb_encode_tag_for_field()
• encode payload with pb_encode_submessage()

For decoding you just decode the submessage one by one. No need to iterate manually, since Protobuf knows the field type (by tag encoded) and length that was put by submessage encoding call. That should be it.

@vpetrigo
Copy link
Author

@ssevans87 would you tell whether snippet above was helpful?

@shockzort
Copy link

shockzort commented Apr 6, 2022

@vpetrigo Hello. I can tell, the snippet above was very helpful for me.
due to your example I was able to pack and unpack something more complex. Thanks!

@Sahat-fahim
Copy link

Thanks a lot for the snippet!!! it was very helpful for me.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment