Exemple #1
0
/*!
 * Add an entry to a journal.
 *
 * If allocation fails, the journal's internal state is fully recoverable.
 *
 * \param[in,out] journal Journal
 * \param[in]     origin  Origin
 * \param[in]     offset  Offset
 * \param[in]     delta   Delta
 * \return                Error code
 */
extern pb_error_t
pb_journal_log(
    pb_journal_t *journal,
    size_t origin, size_t offset, ptrdiff_t delta) {
  assert(journal && origin <= offset && delta);
  if (unlikely_(!pb_journal_valid(journal)))
    return PB_ERROR_INVALID;

  /* If no bulk slot is available, grow bulk */
  if (!journal->entry.bulk) {
    pb_journal_entry_t *new_data = pb_allocator_realloc(
      journal->allocator, journal->entry.data,
        sizeof(pb_journal_entry_t) * journal->entry.size << 1);
    if (new_data) {
      journal->entry.data = new_data;
      journal->entry.bulk = journal->entry.size;
    } else {
      return PB_ERROR_ALLOC;
    }
  }

  /* Create and append a new journal entry */
  pb_journal_entry_t entry = {
    .origin = origin,
    .offset = offset,
    .delta  = delta
  };
  journal->entry.data[journal->entry.size++] = entry;
  journal->entry.bulk--;
  return PB_ERROR_NONE;
}
Exemple #2
0
/*!
 * Adjust the length prefix of the part by the provided delta to reflect the
 * current length of the part, and return the (possibly) adjusted delta.
 *
 * \param[in,out] part  Part
 * \param[in,out] delta Delta
 * \return              Error code
 */
static pb_error_t
adjust_prefix(pb_part_t *part, ptrdiff_t *delta) {
  assert(part && part->offset.diff.length);
  assert(pb_part_valid(part) && pb_part_aligned(part));
  pb_error_t error = PB_ERROR_NONE;

  /* Write new length prefix to binary */
  uint32_t bytes = pb_part_size(part);
  pb_binary_buffer_t buffer = pb_binary_buffer_create();
  if (!(error = pb_binary_buffer_write_varint32(&buffer, &bytes))) {
    ptrdiff_t adjust = pb_binary_buffer_size(&buffer)
                     + part->offset.diff.length;
    if (likely_(!adjust)) {
      error = pb_binary_write(part->binary,
        part->offset.start + part->offset.diff.length, part->offset.start,
          pb_binary_buffer_data(&buffer), pb_binary_buffer_size(&buffer));

    /* Length of length prefix changed */
    } else {
      pb_journal_t *journal = pb_binary_journal(part->binary);
      assert(journal);

      /* Write new length prefix to binary and perform manual alignment */
      pb_error_t error = pb_journal_log(journal, part->offset.start
        + part->offset.diff.length, part->offset.start, adjust);
      if (!error) {
        error = pb_binary_write(part->binary,
          part->offset.start + part->offset.diff.length, part->offset.start,
            pb_binary_buffer_data(&buffer), pb_binary_buffer_size(&buffer));
        if (unlikely_(error)) {
          pb_journal_revert(journal);                        /* LCOV_EXCL_LINE */

        /* Update offsets */
        } else {                                             /* LCOV_EXCL_LINE */
          part->version++;
          part->offset.start       += adjust;
          part->offset.end         += adjust;
          part->offset.diff.origin -= adjust;
          part->offset.diff.tag    -= adjust;
          part->offset.diff.length -= adjust;

          /* Adjust delta for subsequent updates */
          *delta += adjust;
        }
      }
    }
  }
  pb_binary_buffer_destroy(&buffer);
  return error;
}
Exemple #3
0
/*!
 * Move a cursor to the next value of a packed field.
 *
 * \param[in,out] cursor Cursor
 * \return               Test result
 */
static int
next_packed(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 of the packed field */
  pb_buffer_t buffer = pb_buffer_create_zero_copy_internal(
    pb_journal_data_from(pb_cursor_journal(cursor), offset->end),
      packed->end - offset->end);

  /* Create stream over temporary buffer */
  pb_stream_t stream = pb_stream_create(&buffer);
  while (pb_stream_left(&stream)) {

    /* Skip field contents to determine length */
    pb_wiretype_t wiretype =
      pb_field_descriptor_wiretype(cursor->current.descriptor);
    if (unlikely_((cursor->error = pb_stream_skip(&stream, wiretype))))
      break;

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

    /* Cleanup and return with success */
    pb_stream_destroy(&stream);
    pb_buffer_destroy(&buffer);
    return 1;
  }

  /* Cleanup to move on to next field */
  pb_stream_destroy(&stream);
  pb_buffer_destroy(&buffer);

  /* Switch back to non-packed context, as end is reached */
  *offset = *packed;
  return packed->end = 0;
}
Exemple #4
0
/*!
 * Align an offset of a specific version according to a journal.
 *
 * This is the core of the journaling functionality, as it allows several
 * different parts to co-exist at the same time, reading or writing values
 * from or to the underlying binary. However, it's very tricky to get right,
 * as there are three major scenarios as well as some edge cases:
 *
 * -# The change happened before the current part, so the part must be moved by
 *    the given delta. There are two cases which can trigger this scenario:
 *
 *    -# Update on a preceding part, either located in the current message, in
 *       the preceding message or in a parent message.
 *
 *    -# Update on the length prefix of the current part, which is done when
 *       altering the contents of a length-prefixed field or message.
 *
 *    \code{.unparsed}
 *      ... [ T ][ L ][ ... ] ...
 *        ^                       (a)
 *        ^^^^^^^^^^^^^^^^
 *                 ^              (b)
 *    \endcode
 *
 * -# The change happened within the current part, so the part must either be
 *    resized or cleared completely:
 *
 *    -# The update happened somewhere inside the current part, which makes it
 *       necessary to resize the current part by the given delta.
 *
 *    -# The update involves the whole part from start to end and a negative
 *       delta is given which matches its size, so the part was cleared.
 *
 *    \code{.unparsed}
 *      ... [ T ][ L ][ ... ] ...
 *                      ^         (a)
 *          ^^^^^^^^^^^^^^^^^     (b)
 *    \endcode
 *
 * -# The change happened outside the current part and the delta is negative,
 *    which means that the current part must be cleared completley.
 *
 *    \code{.unparsed}
 *      ... [ T ][ L ][ ... ] ...
 *        ^^^^^^^^^^^^^^^^^^^^^
 *    \endcode
 *
 * If a parent message is resized, nothing needs to be done, as the current
 * part is contained and seemingly not affected by the update, so this is a
 * case that doesn't need to be explicitly handled. This is repeated until
 * the provided version matches the current version of the journal.
 *
 * \param[in]     journal Journal
 * \param[in,out] version Version
 * \param[in,out] offset  Offset
 * \return                Error code
 */
extern pb_error_t
pb_journal_align(
    const pb_journal_t *journal, pb_version_t *version, pb_offset_t *offset) {
  assert(journal && offset);
  if (unlikely_(!pb_journal_valid(journal)))
    return PB_ERROR_INVALID;

  /* Iterate journal entries until we're up-to-date */
  uint8_t invalid = 0;
  while (*version < journal->entry.size) {
    const pb_journal_entry_t *entry = &journal->entry.data[*version];

    /* Change happened before current part: move */
    if (entry->origin < offset->start &&
        entry->offset < offset->end) {
      offset->start += entry->delta;
      offset->end   += entry->delta;

      /* Apply potential update on relative offsets */
      ptrdiff_t *diff[3] = {
        &(offset->diff.origin),
        &(offset->diff.tag),
        &(offset->diff.length)
      };
      for (size_t d = 0; d < 3; d++)
        if (entry->offset > offset->start + *(diff[d]) - entry->delta)
          *(diff[d]) -= entry->delta;

    /* Change happened within current part: resize or clear */
    } else if (entry->origin >= offset->start + offset->diff.origin &&
               entry->offset <= offset->end) {

      /* Change happened inside current part: resize */
      if (entry->origin >= offset->start) {
        offset->end += entry->delta;

      /* Current part was cleared: clear */
      } else if ((offset->start + offset->diff.origin) -
                 (offset->end   + entry->delta) == 0) {
        offset->start      += offset->diff.origin;
        offset->end        += entry->delta;
        offset->diff.origin = 0;
        offset->diff.tag    = 0;
        offset->diff.length = 0;

        /* Invalidate after alignment */
        invalid = 1;
      }

    /* Change happened outside current part: clear */
    } else if (entry->origin <= offset->start + offset->diff.origin &&
               entry->origin == entry->offset + entry->delta) {
      offset->start       = entry->origin;
      offset->end         = entry->origin;
      offset->diff.origin = 0;
      offset->diff.tag    = 0;
      offset->diff.length = 0;

      /* Invalidate after alignment */
      invalid = 1;
    }
    (*version)++;
  }

  /* Invalidate part */
  if (invalid)
    *version = SIZE_MAX;
  return *version == SIZE_MAX
    ? PB_ERROR_INVALID
    : PB_ERROR_NONE;
}
Exemple #5
0
/*!
 * Initialize a part.
 *
 * The journal must be tested for writability before writing to the binary, as
 * the binary write is irreversible, while the journal could be reverted.
 *
 * \warning Some lines excluded from code coverage are impossible to occur,
 * but they are necessary to ensure application-wide proper error handling.
 * Others, especially those concerning the journal and binary, are not
 * testable within the context of a part.
 *
 * \param[in,out] part       Part
 * \param[in]     descriptor Field descriptor
 */
static void
init(pb_part_t *part, const pb_field_descriptor_t *descriptor) {
  assert(part && descriptor);
  assert(pb_part_aligned(part));

  /* Initialize field for writing */
  pb_binary_buffer_t buffer = pb_binary_buffer_create();
  do {
    pb_wiretype_t wiretype = pb_field_descriptor_wiretype(descriptor);
    pb_tag_t tag = pb_field_descriptor_tag(descriptor);

    /* Write tag to buffer and backup offsets */
    uint32_t value = wiretype | (tag << 3);
    if (pb_binary_buffer_write_varint32(&buffer, &value))
      break;                                               /* LCOV_EXCL_LINE */
    part->offset.diff.tag    = part->offset.start;
    part->offset.diff.length = part->offset.start
                             + pb_binary_buffer_size(&buffer);

    /* Write default length of zero for length-prefixed fields */
    if (wiretype == PB_WIRETYPE_LENGTH) {
      uint32_t bytes = 0;
      if (pb_binary_buffer_write_varint32(&buffer, &bytes))
        break;                                             /* LCOV_EXCL_LINE */
    }

    /* New data must be written to part, so obtain journal */
    pb_journal_t *journal = pb_binary_journal(part->binary);
    assert(journal);

    /* Write buffer to binary and perform manual alignment */
    pb_error_t error = pb_journal_log(journal,
      part->offset.start + part->offset.diff.origin,
      part->offset.end, pb_binary_buffer_size(&buffer));
    if (!error) {
      size_t offset = pb_binary_buffer_size(&buffer);
      error = pb_binary_write(part->binary,
        part->offset.start, part->offset.end,
          pb_binary_buffer_data(&buffer), pb_binary_buffer_size(&buffer));
      if (unlikely_(error)) {
        pb_journal_revert(journal);                        /* LCOV_EXCL_LINE */

      /* Update offsets */
      } else {                                             /* LCOV_EXCL_LINE */
        part->version++;
        part->offset.start       += offset;
        part->offset.end         += pb_binary_buffer_size(&buffer);
        part->offset.diff.origin -= offset;
        part->offset.diff.tag    -= part->offset.start;
        part->offset.diff.length -= part->offset.start;

        /* Recursive length prefix update of parent messages */
        if (!adjust(part, pb_binary_buffer_size(&buffer))) {
          pb_binary_buffer_destroy(&buffer);
          return;
        }
      }
    }                                                      /* LCOV_EXCL_LINE */
  } while (0);                                             /* LCOV_EXCL_LINE */

  /* Yes. You pulled a Pobert */
  pb_binary_buffer_destroy(&buffer);                       /* LCOV_EXCL_LINE */
  pb_part_invalidate(part);                                /* LCOV_EXCL_LINE */
}
Exemple #6
0
/*!
 * Perform a recursive length prefix update on all messages containing the
 * given part and possibly the part as such up to its origin.
 *
 * This is where all the magic happens. This function takes a binary stream and
 * reads data from it, until it reaches a length-prefixed field. Messages are
 * always length-prefixed, so binary parts that are not can be safely skipped.
 * If the binary stream's current offset matches the tag offset of the part
 * exactly, and the part is empty, we ran into an initialization update, so we
 * can directly abort here.
 *
 * Otherwise, if the binary part contains the part, and thus does not match
 * exactly, we must have found a submessage and need to recurse. The length
 * prefixes must be updated from within, since they are encoded as
 * variable-sized integers and may also affect the size of parent messages.
 *
 * After recursing, it is mandatory to check if the part needs to be
 * realigned, since there may have been updates on the length prefixes of
 * submessages that affect the offsets of the part, and perform such a
 * realignment if necessary.
 *
 * Finally, we write the new length prefix and adjust the delta value if the
 * new length prefix exceeds the length of the current one. That easy.
 *
 * \warning The lines excluded from code coverage are impossible to occur,
 * but they are necessary to ensure application-wide proper error handling.
 *
 * \param[in,out] part   Part
 * \param[in,out] stream Binary stream
 * \param[in,out] delta  Delta
 * \return               Error code
 */
static pb_error_t
adjust_recursive(
    pb_part_t *part, pb_binary_stream_t *stream, ptrdiff_t *delta) {
  assert(part && stream && delta && *delta);
  assert(pb_part_aligned(part));
  pb_error_t error = PB_ERROR_NONE;

  /* Read from the binary stream until the end of the part */
  while (pb_binary_stream_offset(stream) < part->offset.end) {
    if (pb_binary_stream_offset(stream) == part->offset.start - *delta &&
        pb_part_empty(part))
      break;

    /* Skip non-length-prefixed fields */
    pb_tag_t tag = 0; uint32_t bytes;
    size_t offset = pb_binary_stream_offset(stream);
    if ((error = pb_binary_stream_read_varint32(stream, &tag)))
      break;                                               /* LCOV_EXCL_LINE */
    if ((tag & 7) != PB_WIRETYPE_LENGTH) {
      if (!skip_jump[tag & 7] || (error = skip_jump[tag & 7](stream)))
        break;                                             /* LCOV_EXCL_LINE */
      continue;
    }

    /* Create a temporary part, so we can use adjust_prefix() */
    pb_part_t temp = {
      .binary  = pb_part_binary(part),
      .version = pb_part_version(part),
      .offset  = {
        .start = 0,
        .end   = 0,
        .diff  = {
          .origin = offset,
          .tag    = offset,
          .length = pb_binary_stream_offset(stream)
        }
      }
    };

    /* Read length from length-prefixed field */
    if ((error = pb_binary_stream_read_varint32(stream, &bytes)))
      break;                                               /* LCOV_EXCL_LINE */

    /* Update offsets */
    temp.offset.start        = pb_binary_stream_offset(stream);
    temp.offset.end          = temp.offset.start + bytes;
    temp.offset.diff.origin -= temp.offset.start;
    temp.offset.diff.tag    -= temp.offset.start;
    temp.offset.diff.length -= temp.offset.start;

    /* Abort, if we're past the origin */
    if (temp.offset.start > part->offset.start + part->offset.diff.origin)
      break;

    /* The temporary part lies within the part */
    if (temp.offset.start <= part->offset.start &&
        temp.offset.end   >= part->offset.end - *delta) {
      temp.offset.end += *delta;

      /* The parts don't match, so recurse */
      if (temp.offset.start != part->offset.start ||
          temp.offset.end   != part->offset.end) {
        pb_binary_stream_t substream = pb_binary_stream_create_at(
          pb_binary_stream_binary(stream), temp.offset.start);
        error = adjust_recursive(part, &substream, delta);
        pb_binary_stream_destroy(&substream);
        if (unlikely_(error))
          break;                                           /* LCOV_EXCL_LINE */

        /* Parts may be unaligned due to length prefix update */
        if ((!pb_part_aligned(part)  && (error = pb_part_align(part))) ||
            (!pb_part_aligned(&temp) && (error = pb_part_align(&temp))))
          break;                                           /* LCOV_EXCL_LINE */
      }

      /* Adjust length prefix */
      error = adjust_prefix(&temp, delta);
      break;

    /* Otherwise just skip binary part */
    } else if ((error = pb_binary_stream_skip(stream, bytes))) {
      break;                                               /* LCOV_EXCL_LINE */
    }
  }

  /* Invalidate part on error */
  if (unlikely_(error))
    pb_part_invalidate(part);                              /* LCOV_EXCL_LINE */
  return error;
}