mirror of
https://github.com/protobuf-c/protobuf-c.git
synced 2024-12-27 13:31:02 +08:00
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:
parent
060c071209
commit
791b0458bf
@ -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,
|
||||
|
@ -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;
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user