diff --git a/protobuf-c/protobuf-c.c b/protobuf-c/protobuf-c.c index 75f1377..9f99d18 100644 --- a/protobuf-c/protobuf-c.c +++ b/protobuf-c/protobuf-c.c @@ -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, diff --git a/protobuf-c/protobuf-c.h b/protobuf-c/protobuf-c.h index de68544..11eb642 100644 --- a/protobuf-c/protobuf-c.h +++ b/protobuf-c/protobuf-c.h @@ -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;