Beispiel #1
0
/*!
 * Create a part from a message for a specific tag.
 *
 * This method returns a part to read or alter a certain field or submessage of
 * a message. However, how and when a field or submessage is created in the
 * underlying binary depends on the label of the part, which is defined in the
 * message's descriptor. There are two cases:
 *
 * -# Required and optional parts are directly returned when they are already
 *    contained in the message. Otherwise, they are created and returned empty.
 *
 * -# Repeated parts are always appended to the end of the list of already
 *    contained parts with the same tag when created. Dedicated instances of
 *    repeated parts can be accessed through cursors.
 *
 * At first we try to find the exact offset of the specified part. If there is
 * no exact offset, meaning the part does currently not exist, we record the
 * best matching offset, at which we would expect the part to reside. This is
 * just after the part with the previously smaller (or same) tag.
 *
 * There are a few possible scenarios to consider:
 *
 * -# The cursor is valid, the tag matches exactly and the message is not
 *    repeated. In this case we found an existing part and can return it.
 *
 * -# The cursor is valid but the tag does not match exactly or the part is
 *    repeated, so we create a new part.
 *
 * -# The cursor is not valid, in which case we're at the beginning of the
 *    message, so we create a new part.
 *
 * When the second case is true, we know that the message is either empty, or
 * the part we're looking for does not exist. Therefore we create an empty part
 * and return it.
 *
 * \warning New parts always come without values, so only the tag (and length
 * prefix, if applicable) are present. However, empty fields are not valid and
 * not recognized by the parser (except empty strings), so the caller must
 * ensure that a value is set directly after creating a new part.
 *
 * \warning Merged messages are not implemented as demanded in the Protocol
 * Buffers standard. This would make it necessary to scan the entire binary
 * every time, as there may be overrides for required or optional fields. This
 * is orthogonal to the philosophy of this library, and is thus omitted.
 *
 * \warning The existence of the descriptor field is checked with an assertion,
 * as the tag is assumed to be specified at compile time.
 *
 * \param[in,out] message Message
 * \param[in]     tag     Tag
 * \return                Part
 */
extern pb_part_t
pb_part_create(pb_message_t *message, pb_tag_t tag) {
  assert(message && tag);
  if (pb_message_valid(message) && !pb_message_align(message)) {
    const pb_field_descriptor_t *descriptor =
      pb_message_descriptor_field_by_tag(pb_message_descriptor(message), tag);
    assert(descriptor);

    /* Determine exact or best matching field offset */
    pb_cursor_t cursor = pb_cursor_create_without_tag(message);
    pb_cursor_t temp   = pb_cursor_copy(&cursor);
    if (pb_cursor_valid(&temp)) {
      do {
        if (pb_cursor_tag(&temp) <= tag) {
          pb_cursor_destroy(&cursor);
          cursor = pb_cursor_copy(&temp);
        }
      } while (pb_cursor_next(&temp));
    }

    /* Don't indicate an error, if the cursor just reached the end */
    if (pb_cursor_valid(&temp) ||
        pb_cursor_error(&temp) == PB_ERROR_OFFSET) {

      /* If the tag matches and the field is non-repeated, we're done */
      size_t start = pb_message_start(message);
      if (pb_cursor_valid(&cursor)) {
        if (pb_cursor_tag(&cursor) == tag &&
            pb_field_descriptor_label(descriptor) != PB_LABEL_REPEATED) {
          pb_part_t part = pb_part_create_from_cursor(&cursor);
          pb_cursor_destroy(&cursor);
          return part;
        }

        /* Otherwise correct insert vector in documented cases */
        const pb_offset_t *offset = pb_cursor_current(&cursor);
        if (pb_cursor_pos(&cursor) || pb_cursor_tag(&cursor) <= tag)
          start = offset->end;
      }
      pb_cursor_destroy(&cursor);

      /* Create and return an empty part */
      pb_part_t part = {
        .binary  = pb_message_binary(message),
        .version = pb_message_version(message),
        .offset  = {
          .start = start,
          .end   = start,
          .diff  = {
            .origin = pb_message_start(message) - start,
            .tag    = 0,
            .length = 0
          }
        }
      };
      init(&part, descriptor);
      return part;
    }
    pb_cursor_destroy(&temp);
  }
Beispiel #2
0
/*!
 * Move a cursor to the next field.
 *
 * \param[in,out] cursor Cursor
 * \return               Test result
 */
static int
next(pb_cursor_t *cursor) {
  assert(cursor);
  pb_offset_t *offset = &(cursor->current.offset),
              *packed = &(cursor->current.packed);

  /* Create temporary buffer to read the next value */
  pb_buffer_t buffer = pb_buffer_create_zero_copy_internal(
    pb_journal_data_from(pb_cursor_journal(cursor), 0),
      pb_message_end(&(cursor->message)));

  /* Create stream over temporary buffer */
  pb_stream_t stream = pb_stream_create_at(&buffer, offset->end);
  while (pb_stream_left(&stream)) {

    /* Adjust offsets */
    offset->start       = offset->end;
    offset->diff.origin = pb_message_start(&(cursor->message));
    offset->diff.tag    = pb_stream_offset(&stream);

    /* Read tag from stream */
    pb_tag_t tag; uint32_t length;
    if ((cursor->error = pb_stream_read(&stream, PB_TYPE_UINT32, &tag)))
      break;

    /* Extract wiretype and tag */
    pb_wiretype_t wiretype = tag & 7;
    tag >>= 3;

    /* Skip field contents to determine length */
    offset->diff.length = pb_stream_offset(&stream);
    if (wiretype == PB_WIRETYPE_LENGTH) {
      if ((cursor->error = pb_stream_read(&stream, PB_TYPE_UINT32, &length)))
        break;
      offset->start = pb_stream_offset(&stream);
      if ((cursor->error = pb_stream_advance(&stream, length)))
        break;
    } else {
      offset->start = pb_stream_offset(&stream);
      if ((cursor->error = pb_stream_skip(&stream, wiretype)))
        break;
    }

    /* Adjust offsets */
    offset->end          = pb_stream_offset(&stream);
    offset->diff.origin -= offset->start;
    offset->diff.tag    -= offset->start;
    offset->diff.length -= offset->start;

    /* If a tag is set check if the tags match or continue */
    if (cursor->tag && cursor->tag != tag) {
      continue;

    /* Otherwise try to load descriptor for current tag */
    } else if (!cursor->current.descriptor ||
        pb_field_descriptor_tag(cursor->current.descriptor) != tag) {
      if (!(cursor->current.descriptor = pb_descriptor_field_by_tag(
          pb_message_descriptor(&(cursor->message)), tag)))
        continue;
    }

    /* Switch to packed context in case of packed field */
    if (wiretype != pb_field_descriptor_wiretype(cursor->current.descriptor) &&
        wiretype == PB_WIRETYPE_LENGTH) {
      *packed = *offset;

      /* Prepare offsets for packed field members */
      offset->end         = offset->start;
      offset->diff.tag    = 0;
      offset->diff.length = 0;
    }

    /* Cleanup and return */
    pb_stream_destroy(&stream);
    pb_buffer_destroy(&buffer);
    return !packed->end;
  }

  /* Invalidate cursor if at end */
  if (!(pb_stream_left(&stream) && cursor->error))
    cursor->error = PB_ERROR_EOM;

  /* Cleanup and return */
  pb_stream_destroy(&stream);
  pb_buffer_destroy(&buffer);
  return 0;
}