protobuf-c.{c,h}: Implement oneof support

Add a field flag and functions to pack/unpack oneofs
Add logic to parse multiple fields for the same oneof
Add logic for merging submessages that contain oneofs
This commit is contained in:
Ilya Lipnitskiy 2014-11-05 15:04:52 -08:00
parent 060c071209
commit 791b0458bf
2 changed files with 267 additions and 67 deletions

View File

@ -460,6 +460,39 @@ required_field_get_packed_size(const ProtobufCFieldDescriptor *field,
return 0;
}
/**
* Calculate the serialized size of a single oneof message field, including
* the space needed by the preceding tag. Returns 0 if the oneof field isn't
* selected or is not set.
*
* \param field
* Field descriptor for member.
* \param oneof_case
* A pointer to the case enum that selects the field in the oneof.
* \param member
* Field to encode.
* \return
* Number of bytes required.
*/
static size_t
oneof_field_get_packed_size(const ProtobufCFieldDescriptor *field,
const uint32_t *oneof_case,
const void *member)
{
if (*oneof_case == field->id) {
if (field->type == PROTOBUF_C_TYPE_MESSAGE ||
field->type == PROTOBUF_C_TYPE_STRING)
{
const void *ptr = *(const void * const *) member;
if (ptr == NULL || ptr == field->default_value)
return 0;
}
} else {
return 0;
}
return required_field_get_packed_size(field, member);
}
/**
* Calculate the serialized size of a single optional message field, including
* the space needed by the preceding tag. Returns 0 if the optional field isn't
@ -621,7 +654,10 @@ size_t protobuf_c_message_get_packed_size(const ProtobufCMessage *message)
if (field->label == PROTOBUF_C_LABEL_REQUIRED) {
rv += required_field_get_packed_size(field, member);
} else if (field->label == PROTOBUF_C_LABEL_OPTIONAL) {
rv += optional_field_get_packed_size(field, qmember, member);
if (0 != (field->flags & PROTOBUF_C_FIELD_FLAG_ONEOF))
rv += oneof_field_get_packed_size(field, qmember, member);
else
rv += optional_field_get_packed_size(field, qmember, member);
} else {
rv += repeated_field_get_packed_size(
field,
@ -1018,6 +1054,40 @@ required_field_pack(const ProtobufCFieldDescriptor *field,
return 0;
}
/**
* Pack a oneof field and return the number of bytes written. Only packs the
* field that is selected by the case enum.
*
* \param field
* Field descriptor.
* \param oneof_case
* A pointer to the case enum that selects the field in the oneof.
* \param member
* The field member.
* \param[out] out
* Packed value.
* \return
* Number of bytes written to `out`.
*/
static size_t
oneof_field_pack(const ProtobufCFieldDescriptor *field,
const uint32_t *oneof_case,
const void *member, uint8_t *out)
{
if (*oneof_case == field->id) {
if (field->type == PROTOBUF_C_TYPE_MESSAGE ||
field->type == PROTOBUF_C_TYPE_STRING)
{
const void *ptr = *(const void * const *) member;
if (ptr == NULL || ptr == field->default_value)
return 0;
}
} else {
return 0;
}
return required_field_pack(field, member, out);
}
/**
* Pack an optional field and return the number of bytes written.
*
@ -1312,6 +1382,7 @@ protobuf_c_message_pack(const ProtobufCMessage *message, uint8_t *out)
* quantifier field of the structure), but the pointer is only
* valid if the field is:
* - a repeated field, or
* - a field that is part of a oneof
* - an optional field that isn't a pointer type
* (Meaning: not a message or a string).
*/
@ -1321,11 +1392,10 @@ protobuf_c_message_pack(const ProtobufCMessage *message, uint8_t *out)
if (field->label == PROTOBUF_C_LABEL_REQUIRED) {
rv += required_field_pack(field, member, out + rv);
} else if (field->label == PROTOBUF_C_LABEL_OPTIONAL) {
/*
* Note that qmember is bogus for strings and messages,
* but it isn't used.
*/
rv += optional_field_pack(field, qmember, member, out + rv);
if (0 != (field->flags & PROTOBUF_C_FIELD_FLAG_ONEOF))
rv += oneof_field_pack (field, qmember, member, out + rv);
else
rv += optional_field_pack(field, qmember, member, out + rv);
} else {
rv += repeated_field_pack(field, *(const size_t *) qmember,
member, out + rv);
@ -1459,6 +1529,39 @@ required_field_pack_to_buffer(const ProtobufCFieldDescriptor *field,
return rv;
}
/**
* Pack a oneof field to a buffer. Only packs the field that is selected by the case enum.
*
* \param field
* Field descriptor.
* \param oneof_case
* A pointer to the case enum that selects the field in the oneof.
* \param member
* The element to be packed.
* \param[out] buffer
* Virtual buffer to append data to.
* \return
* Number of bytes serialised to `buffer`.
*/
static size_t
oneof_field_pack_to_buffer(const ProtobufCFieldDescriptor *field,
const uint32_t *oneof_case,
const void *member, ProtobufCBuffer *buffer)
{
if (*oneof_case == field->id) {
if (field->type == PROTOBUF_C_TYPE_MESSAGE ||
field->type == PROTOBUF_C_TYPE_STRING)
{
const void *ptr = *(const void *const *) member;
if (ptr == NULL || ptr == field->default_value)
return 0;
}
} else {
return 0;
}
return required_field_pack_to_buffer(field, member, buffer);
}
/**
* Pack an optional field to a buffer.
*
@ -1735,12 +1838,21 @@ protobuf_c_message_pack_to_buffer(const ProtobufCMessage *message,
if (field->label == PROTOBUF_C_LABEL_REQUIRED) {
rv += required_field_pack_to_buffer(field, member, buffer);
} else if (field->label == PROTOBUF_C_LABEL_OPTIONAL) {
rv += optional_field_pack_to_buffer(
field,
qmember,
member,
buffer
);
if (0 != (field->flags & PROTOBUF_C_FIELD_FLAG_ONEOF)) {
rv += oneof_field_pack_to_buffer(
field,
qmember,
member,
buffer
);
} else {
rv += optional_field_pack_to_buffer(
field,
qmember,
member,
buffer
);
}
} else {
rv += repeated_field_pack_to_buffer(
field,
@ -1910,7 +2022,7 @@ merge_messages(ProtobufCMessage *earlier_msg,
{
unsigned i;
const ProtobufCFieldDescriptor *fields =
earlier_msg->descriptor->fields;
latter_msg->descriptor->fields;
for (i = 0; i < latter_msg->descriptor->n_fields; i++) {
if (fields[i].label == PROTOBUF_C_LABEL_REPEATED) {
size_t *n_earlier =
@ -1958,40 +2070,62 @@ merge_messages(ProtobufCMessage *earlier_msg,
*n_earlier = 0;
*p_earlier = 0;
}
} else if (fields[i].type == PROTOBUF_C_TYPE_MESSAGE) {
ProtobufCMessage **em =
STRUCT_MEMBER_PTR(ProtobufCMessage *,
earlier_msg,
fields[i].offset);
ProtobufCMessage **lm =
STRUCT_MEMBER_PTR(ProtobufCMessage *,
latter_msg,
fields[i].offset);
if (*em != NULL) {
if (*lm != NULL) {
if (!merge_messages
(*em, *lm, allocator))
return FALSE;
} else {
/* Zero copy the optional message */
assert(fields[i].label ==
PROTOBUF_C_LABEL_OPTIONAL);
*lm = *em;
*em = NULL;
}
}
} else if (fields[i].label == PROTOBUF_C_LABEL_OPTIONAL) {
size_t el_size = 0;
const ProtobufCFieldDescriptor *field;
uint32_t *earlier_case_p = STRUCT_MEMBER_PTR(uint32_t,
earlier_msg,
fields[i].
quantifier_offset);
uint32_t *latter_case_p = STRUCT_MEMBER_PTR(uint32_t,
latter_msg,
fields[i].
quantifier_offset);
if (fields[i].flags & PROTOBUF_C_FIELD_FLAG_ONEOF) {
if (*latter_case_p == 0) {
/* lookup correct oneof field */
int field_index =
int_range_lookup(
latter_msg->descriptor
->n_field_ranges,
latter_msg->descriptor
->field_ranges,
*earlier_case_p);
field = latter_msg->descriptor->fields +
field_index;
} else {
/* Oneof is present in the latter message, move on */
continue;
}
} else {
field = &fields[i];
}
protobuf_c_boolean need_to_merge = FALSE;
void *earlier_elem =
STRUCT_MEMBER_P(earlier_msg, fields[i].offset);
STRUCT_MEMBER_P(earlier_msg, field->offset);
void *latter_elem =
STRUCT_MEMBER_P(latter_msg, fields[i].offset);
const void *def_val = fields[i].default_value;
STRUCT_MEMBER_P(latter_msg, field->offset);
const void *def_val = field->default_value;
switch (fields[i].type) {
switch (field->type) {
case PROTOBUF_C_TYPE_MESSAGE: {
ProtobufCMessage *em = *(ProtobufCMessage **) earlier_elem;
ProtobufCMessage *lm = *(ProtobufCMessage **) latter_elem;
if (em != NULL) {
if (lm != NULL) {
if (!merge_messages(em, lm, allocator))
return FALSE;
/* Already merged */
need_to_merge = FALSE;
} else {
/* Zero copy the message */
need_to_merge = TRUE;
}
}
break;
}
case PROTOBUF_C_TYPE_BYTES: {
el_size = sizeof(ProtobufCBinaryData);
uint8_t *e_data =
((ProtobufCBinaryData *) earlier_elem)->data;
uint8_t *l_data =
@ -2009,7 +2143,6 @@ merge_messages(ProtobufCMessage *earlier_msg,
break;
}
case PROTOBUF_C_TYPE_STRING: {
el_size = sizeof(char *);
char *e_str = *(char **) earlier_elem;
char *l_str = *(char **) latter_elem;
const char *d_str = def_val;
@ -2018,20 +2151,18 @@ merge_messages(ProtobufCMessage *earlier_msg,
break;
}
default: {
el_size = sizeof_elt_in_repeated_array(fields[i].type);
need_to_merge =
STRUCT_MEMBER(protobuf_c_boolean,
earlier_msg,
fields[i].quantifier_offset) &&
!STRUCT_MEMBER(protobuf_c_boolean,
latter_msg,
fields[i].quantifier_offset);
/* Could be has field or case enum, the logic is
* equivalent, since 0 (FALSE) means not set for
* oneof */
need_to_merge = (*earlier_case_p != 0) &&
(*latter_case_p == 0);
break;
}
}
if (need_to_merge) {
size_t el_size =
sizeof_elt_in_repeated_array(field->type);
memcpy(latter_elem, earlier_elem, el_size);
/*
* Reset the element from the old message to 0
@ -2042,16 +2173,11 @@ merge_messages(ProtobufCMessage *earlier_msg,
*/
memset(earlier_elem, 0, el_size);
if (fields[i].quantifier_offset != 0) {
/* Set the has field, if applicable */
STRUCT_MEMBER(protobuf_c_boolean,
latter_msg,
fields[i].
quantifier_offset) = TRUE;
STRUCT_MEMBER(protobuf_c_boolean,
earlier_msg,
fields[i].
quantifier_offset) = FALSE;
if (field->quantifier_offset != 0) {
/* Set the has field or the case enum,
* if applicable */
*latter_case_p = *earlier_case_p;
*earlier_case_p = 0;
}
}
}
@ -2349,6 +2475,64 @@ parse_required_member(ScannedMember *scanned_member,
return FALSE;
}
static protobuf_c_boolean
parse_oneof_member (ScannedMember *scanned_member,
void *member,
ProtobufCMessage *message,
ProtobufCAllocator *allocator)
{
uint32_t *oneof_case = STRUCT_MEMBER_PTR(uint32_t, message,
scanned_member->field->quantifier_offset);
/* If we have already parsed a member of this oneof, free it. */
if (*oneof_case != 0) {
/* lookup field */
int field_index =
int_range_lookup(message->descriptor->n_field_ranges,
message->descriptor->field_ranges,
*oneof_case);
const ProtobufCFieldDescriptor *old_field =
message->descriptor->fields + field_index;
switch (old_field->type) {
case PROTOBUF_C_TYPE_STRING: {
char **pstr = member;
const char *def = old_field->default_value;
if (*pstr != NULL && *pstr != def)
do_free(allocator, *pstr);
break;
}
case PROTOBUF_C_TYPE_BYTES: {
ProtobufCBinaryData *bd = member;
const ProtobufCBinaryData *def_bd = old_field->default_value;
if (bd->data != NULL &&
(def_bd == NULL || bd->data != def_bd->data))
{
do_free(allocator, bd->data);
}
break;
}
case PROTOBUF_C_TYPE_MESSAGE: {
ProtobufCMessage **pmessage = member;
const ProtobufCMessage *def_mess = old_field->default_value;
if (*pmessage != NULL && *pmessage != def_mess)
protobuf_c_message_free_unpacked(*pmessage, allocator);
break;
}
default:
break;
}
size_t el_size = sizeof_elt_in_repeated_array(old_field->type);
memset (member, 0, el_size);
}
if (!parse_required_member (scanned_member, member, allocator, TRUE))
return FALSE;
*oneof_case = scanned_member->tag;
return TRUE;
}
static protobuf_c_boolean
parse_optional_member(ScannedMember *scanned_member,
void *member,
@ -2562,8 +2746,13 @@ parse_member(ScannedMember *scanned_member,
return parse_required_member(scanned_member, member,
allocator, TRUE);
case PROTOBUF_C_LABEL_OPTIONAL:
return parse_optional_member(scanned_member, member,
message, allocator);
if (0 != (field->flags & PROTOBUF_C_FIELD_FLAG_ONEOF)) {
return parse_oneof_member(scanned_member, member,
message, allocator);
} else {
return parse_optional_member(scanned_member, member,
message, allocator);
}
case PROTOBUF_C_LABEL_REPEATED:
if (scanned_member->wire_type ==
PROTOBUF_C_WIRE_TYPE_LENGTH_PREFIXED &&
@ -2748,9 +2937,9 @@ protobuf_c_message_unpack(const ProtobufCMessageDescriptor *desc,
goto error_cleanup_during_scan;
}
/*
* \todo Consider optimizing for field[1].id == tag, if field[1]
* exists!
*/
* \todo Consider optimizing for field[1].id == tag, if field[1]
* exists!
*/
if (last_field == NULL || last_field->id != tag) {
/* lookup field */
int field_index =
@ -2977,6 +3166,14 @@ protobuf_c_message_free_unpacked(ProtobufCMessage *message,
allocator = &protobuf_c__allocator;
message->descriptor = NULL;
for (f = 0; f < desc->n_fields; f++) {
if (0 != (desc->fields[f].flags & PROTOBUF_C_FIELD_FLAG_ONEOF) &&
desc->fields[f].id !=
STRUCT_MEMBER(uint32_t, message, desc->fields[f].quantifier_offset))
{
/* This is not the selected oneof, skip it */
continue;
}
if (desc->fields[f].label == PROTOBUF_C_LABEL_REPEATED) {
size_t n = STRUCT_MEMBER(size_t,
message,

View File

@ -239,6 +239,9 @@ typedef enum {
/** Set if the field is marked with the `deprecated` option. */
PROTOBUF_C_FIELD_FLAG_DEPRECATED = (1 << 1),
/** Set if the field is a member of a oneof (union). */
PROTOBUF_C_FIELD_FLAG_ONEOF = (1 << 2),
} ProtobufCFieldFlag;
/**
@ -545,7 +548,7 @@ struct ProtobufCFieldDescriptor {
/**
* The offset in bytes of the message's C structure's quantifier field
* (the `has_MEMBER` field for optional members or the `n_MEMBER` field
* for repeated members.
* for repeated members or the case enum for oneofs).
*/
unsigned quantifier_offset;