| // Copyright 2026 Google LLC |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #ifndef RIEGELI_MESSAGES_SERIALIZED_MESSAGE_ASSEMBLER_H_ |
| #define RIEGELI_MESSAGES_SERIALIZED_MESSAGE_ASSEMBLER_H_ |
| |
| #include <stddef.h> |
| #include <stdint.h> |
| |
| #include <functional> |
| #include <limits> |
| #include <string> |
| #include <type_traits> |
| #include <utility> |
| #include <vector> |
| |
| #include "absl/base/attributes.h" |
| #include "absl/base/nullability.h" |
| #include "absl/base/optimization.h" |
| #include "absl/container/inlined_vector.h" |
| #include "absl/container/linked_hash_map.h" |
| #include "absl/functional/any_invocable.h" |
| #include "absl/status/status.h" |
| #include "absl/strings/cord.h" |
| #include "absl/types/span.h" |
| #include "riegeli/base/bytes_ref.h" |
| #include "riegeli/base/chain.h" |
| #include "riegeli/base/cord_iterator_span.h" |
| #include "riegeli/base/dependency.h" |
| #include "riegeli/bytes/chain_writer.h" |
| #include "riegeli/bytes/cord_writer.h" |
| #include "riegeli/bytes/reader.h" |
| #include "riegeli/bytes/string_writer.h" |
| #include "riegeli/bytes/writer.h" |
| #include "riegeli/messages/field_handler_map.h" |
| #include "riegeli/messages/serialized_message_reader.h" |
| #include "riegeli/messages/serialized_message_writer.h" |
| |
| ABSL_POINTERS_DEFAULT_NONNULL |
| |
| namespace riegeli { |
| |
| // `SerializedMessageAssembler` writes a serialized proto message based on a |
| // serialized base message and added and/or removed fields. |
| // |
| // Parents of added fields are created implicitly if they are not present in the |
| // base message. |
| // |
| // A `SerializedMessageAssembler` object maintains a structure of fields to be |
| // added and/or removed, which can be efficiently reused for multiple messages. |
| // |
| // Usage: |
| // ``` |
| // // Register message structure. |
| // riegeli::SerializedMessageAssembler::Builder message_assembler_builder; |
| // for (const auto& field_name : fields_to_add) { |
| // field_name.parent_for_add = |
| // message_assembler_builder.RegisterParentForAdd( |
| // field_name.parent_path); |
| // } |
| // for (const auto& field_name : fields_to_remove) { |
| // field_name.parent_for_remove = |
| // message_assembler_builder.RegisterFieldForRemove( |
| // field_name.parent_path, field_name.field_number); |
| // } |
| // SerializedMessageAssembler message_assembler( |
| // std::move(message_assembler_builder)); |
| // |
| // // Assemble messages. |
| // for (auto& message : messages) { |
| // riegeli::SerializedMessageAssembler::Session session(message_assembler); |
| // for (const auto& field : message.fields_to_add) { |
| // session.AddField( |
| // field.parent_for_add, |
| // [field_number = field.field_number, value = field.value]( |
| // riegeli::SerializedMessageWriter& message_writer) mutable { |
| // // Example for a string field: |
| // return message_writer.WriteString(field_number, |
| // std::move(value)); |
| // }); |
| // } |
| // for (const auto& field : message.fields_to_remove) { |
| // session.RemoveField(field.parent_for_remove); |
| // } |
| // std::move(session).WriteMessage(message_assembler, base_message, |
| // message.serialized); |
| // } |
| // ``` |
| // |
| // Alternatively, the stages of registration and assembly can be interleaved. |
| // This is reasonable for assembling a single message. This would be inefficient |
| // for multiple messages. |
| // ``` |
| // riegeli::SerializedMessageAssembler::Builder message_assembler_builder; |
| // riegeli::SerializedMessageAssembler::Session session; |
| // for (const auto& field : fields_to_add) { |
| // session.AddField( |
| // message_assembler_builder.RegisterParentForAdd(field.parent_path), |
| // [field_number = field.field_number, value = field.value]( |
| // riegeli::SerializedMessageWriter& message_writer) mutable { |
| // // Example for a string field: |
| // return message_writer.WriteString(field_number, std::move(value)); |
| // }); |
| // } |
| // for (const auto& field : fields_to_remove) { |
| // session.RemoveField(message_assembler_builder.RegisterFieldForRemove( |
| // field.parent_path, field.field_number)); |
| // } |
| // SerializedMessageAssembler message_assembler( |
| // std::move(message_assembler_builder)); |
| // std::move(session).WriteMessage(message_assembler, base_message, |
| // serialized_message); |
| // ``` |
| class SerializedMessageAssembler { |
| public: |
| // A sequence of field numbers identifying a length-delimited field. |
| // |
| // Each element of a `ParentPath` should correspond to a singular submessage |
| // field contained in the previous message, except that the last element may |
| // correspond to a singular `string` or `bytes` field, or to a packed repeated |
| // field. |
| // |
| // An empty `ParentPath` indicates the root message. |
| using ParentPath = absl::Span<const int>; |
| |
| // Identifies the parent of fields to be added, binding |
| // `Builder::RegisterParentForAdd()` with `Session::AddField()`. |
| // Usually treated as an opaque type. |
| struct ParentForAdd { |
| static constexpr uint32_t kUnregistered = |
| std::numeric_limits<uint32_t>::max(); |
| uint32_t raw = kUnregistered; |
| }; |
| |
| // Identifies a field to be removed, binding |
| // `Builder::RegisterFieldForRemove()` with `Session::RemoveField()`. |
| // Usually treated as an opaque type. |
| struct FieldForRemove { |
| static constexpr uint32_t kUnregistered = |
| std::numeric_limits<uint32_t>::max(); |
| uint32_t raw = kUnregistered; |
| }; |
| |
| // Prepares a `SerializedMessageAssembler`. |
| class Builder; |
| |
| // Maintains field values during writing a single message. |
| class Session; |
| |
| // Creates an empty `SerializedMessageAssembler`. Designed for `Reset()`. |
| SerializedMessageAssembler() = default; |
| |
| // Builds a `SerializedMessageAssembler`. |
| explicit SerializedMessageAssembler(Builder&& builder); |
| |
| SerializedMessageAssembler(SerializedMessageAssembler&& that) noexcept; |
| SerializedMessageAssembler& operator=( |
| SerializedMessageAssembler&& that) noexcept; |
| |
| ~SerializedMessageAssembler(); |
| |
| // Makes `*this` equivalent to a newly constructed |
| // `SerializedMessageAssembler`. |
| ABSL_ATTRIBUTE_REINITIALIZES void Reset(); |
| ABSL_ATTRIBUTE_REINITIALIZES void Reset(Builder&& builder); |
| |
| private: |
| // A value to be written to a `SerializedMessageWriter`. Its type varies, |
| // so it is represented as an invocable. |
| using FieldValue = absl::AnyInvocable<absl::Status( |
| SerializedMessageWriter& message_writer) &&>; |
| // Values to be written to a `SerializedMessageWriter` under one parent. |
| using FieldValues = std::vector<FieldValue>; |
| |
| // Which submessages of a given message are present in the base message, |
| // in the order corresponding to `RegisteredMessage::submessages`. |
| using SubmessageSet = absl::InlinedVector<bool, 8>; |
| // An index into `SubmessageSet`. |
| using SubmessageIndex = uint32_t; |
| |
| // Field handlers for reading the base message. |
| using RewriteHandlers = FieldHandlerMap< |
| // Remaining field values to write, indexed by `ParentForAdd`. |
| const absl::Span<FieldValues>, |
| // Fields to remove, indexed by `FieldForRemove`. |
| const absl::Span<const bool>, |
| // Which submessages of this message have been rewritten, in the order |
| // corresponding to `RegisteredMessage::submessages`. Submessages absent |
| // in the base message will be created at the end of the message. |
| const absl::Span<bool>, |
| // The destination message writer. |
| SerializedMessageWriter>; |
| |
| // Copies unhandled fields. |
| using CopyingHandler = |
| CopyingFieldHandler<const absl::Span<FieldValues>, |
| const absl::Span<const bool>, const absl::Span<bool>, |
| SerializedMessageWriter>; |
| |
| // During registration, maintains information about a field or root. |
| struct RegisteredFieldBuilder { |
| ParentForAdd parent_for_add; |
| FieldForRemove field_for_remove; // Not used in root. |
| absl::linked_hash_map<int, RegisteredFieldBuilder> children; |
| }; |
| |
| // During assembly, maintains information about a message, including root. |
| struct RegisteredMessage { |
| RegisteredMessage() noexcept : field_number(0) {} |
| explicit RegisteredMessage(int field_number) : field_number(field_number) {} |
| |
| int field_number; // Not used in root. |
| ParentForAdd parent_for_add; |
| // In registration order. Must not be reallocated after initialization. |
| std::vector<RegisteredMessage> submessages; |
| // `handlers` may refer to elements of `submessages`. |
| RewriteHandlers handlers; |
| }; |
| |
| struct LeafHandler; |
| struct SubmessageHandler; |
| |
| static RegisteredMessage Build(int field_number, |
| RegisteredFieldBuilder&& message_builder) { |
| RegisteredMessage message; |
| Build(field_number, std::move(message_builder), message); |
| return message; |
| } |
| |
| static void Build(int field_number, RegisteredFieldBuilder&& message_builder, |
| RegisteredMessage& message); |
| |
| // Copies fields, rewriting submessages present in the base message. |
| template <typename Src> |
| static absl::Status RewriteFields(absl::Span<FieldValues> fields_to_add, |
| absl::Span<const bool> fields_to_remove, |
| const RegisteredMessage& message, Src&& src, |
| SerializedMessageWriter& message_writer); |
| |
| // Writes fields from submessages which were not present in the base message, |
| // and fields added directly to the message. Creates required parent |
| // submessages if they turn out to be non-empty. |
| static absl::Status WriteNewFields( |
| absl::Span<FieldValues> fields_to_add, const RegisteredMessage& message, |
| absl::Span<const bool> submessages_rewritten, |
| SerializedMessageWriter& message_writer); |
| |
| size_t num_fields_for_add() const { return size_t{num_fields_for_add_.raw}; } |
| size_t num_fields_for_remove() const { |
| return size_t{num_fields_for_remove_.raw}; |
| } |
| |
| ParentForAdd num_fields_for_add_ = {/*raw=*/0}; |
| FieldForRemove num_fields_for_remove_ = {/*raw=*/0}; |
| RegisteredMessage root_; |
| }; |
| |
| class SerializedMessageAssembler::Builder { |
| public: |
| // Creates an empty `SerializedMessageAssembler::Builder`. |
| Builder() = default; |
| |
| Builder(Builder&& that) noexcept; |
| Builder& operator=(Builder&& that) noexcept; |
| |
| ~Builder(); |
| |
| // Makes `*this` equivalent to a newly constructed |
| // `SerializedMessageAssembler::Builder`. |
| ABSL_ATTRIBUTE_REINITIALIZES void Reset(); |
| |
| // Registers interest in writing fields under this parent. |
| // |
| // If the parent message is present in the base message (and not removed), |
| // and/or is used for multiple fields, then its contents are merged. If it is |
| // present multiple times in the base message, then contents are merged to its |
| // first occurrence. |
| // |
| // Returns a `ParentForAdd` to be passed to `Session::AddField()`. |
| // |
| // Can be called multiple times with the same `parent_path`, returning the |
| // same `ParentForAdd` each time. |
| ParentForAdd RegisterParentForAdd(ParentPath parent_path); |
| |
| // Registers interest in removing this field from the base message. |
| // |
| // Returns a `FieldForRemove` to be passed to `Session::RemoveField()`. |
| // |
| // Can be called multiple times with the same `parent_path` and |
| // `field_number`, returning the same `FieldForRemove` each time. |
| FieldForRemove RegisterFieldForRemove(ParentPath parent_path, |
| int field_number); |
| |
| private: |
| friend class SerializedMessageAssembler; // For member variables. |
| |
| RegisteredFieldBuilder& RegisterParentInternal(ParentPath parent_path); |
| |
| ParentForAdd next_parent_for_add_ = {/*raw=*/0}; |
| FieldForRemove next_field_for_remove_ = {/*raw=*/0}; |
| RegisteredFieldBuilder root_builder_; |
| }; |
| |
| class SerializedMessageAssembler::Session { |
| private: |
| template <typename Src> |
| struct IsSource |
| : std::disjunction<TargetRefSupportsDependency<Reader*, Src>, |
| std::is_convertible<Src&&, BytesRef>, |
| std::is_convertible<Src&&, Chain>, |
| std::is_convertible<Src&&, const absl::Cord&>, |
| std::is_convertible<Src&&, CordIteratorSpan>> {}; |
| |
| template <typename Dest> |
| struct IsDestination |
| : std::disjunction<TargetRefSupportsDependency<Writer*, Dest>, |
| std::is_convertible<Dest&&, std::string&>, |
| std::is_convertible<Dest&&, Chain&>, |
| std::is_convertible<Dest&&, absl::Cord&>> {}; |
| |
| public: |
| // The constructor to use when the `SerializedMessageAssembler` does not have |
| // registered fields yet. In this case there is no benefit in passing the |
| // `SerializedMessageAssembler` to the constructor. |
| Session() = default; |
| |
| // The constructor to use when the `SerializedMessageAssembler` has fields |
| // already registered. In this case this constructor is more efficient than |
| // the default constructor. |
| explicit Session(const SerializedMessageAssembler& assembler) |
| : fields_to_add_(assembler.num_fields_for_add()), |
| fields_to_remove_(assembler.num_fields_for_remove()) {} |
| |
| // The source `Session` is left empty. |
| Session(Session&& that) noexcept; |
| Session& operator=(Session&& that) noexcept; |
| |
| ~Session(); |
| |
| // Makes `*this` equivalent to a newly constructed `Session`. |
| ABSL_ATTRIBUTE_REINITIALIZES void Reset(); |
| ABSL_ATTRIBUTE_REINITIALIZES void Reset( |
| const SerializedMessageAssembler& assembler); |
| |
| // Adds the value of a field identified by `parent_for_add`, which was |
| // returned by `Builder::RegisterParentForAdd()`. |
| // |
| // `action` will be called exactly once by a successful `WriteMessage()`, |
| // with a `SerializedMessageWriter&` parameter. It should write the field |
| // there and return `absl::Status`. |
| // |
| // If the parent message is present in the base message (and not removed), |
| // and/or `AddField()` is called multiple times with the same |
| // `parent_for_add`, then the contents of the parent message are merged. If it |
| // is present multiple times in the base message, then contents are merged to |
| // its first occurrence. |
| // |
| // If multiple fields are written to a parent message, then they are written |
| // separately rather than merged. |
| // |
| // The consequences of having multiple occurrences of a field follow the proto |
| // semantics of merging: |
| // |
| // * A singular non-message field overrides the previous value. |
| // * A singular message field is merged with the previous value. |
| // * A repeated field has a new element added. |
| // |
| // In the case of a singular field or a packed repeated field, this makes the |
| // serialized representation non-canonical or with redundant overridden data. |
| // In these cases it is better to avoid having multiple occurrences. Even |
| // though regular proto parsing merges these occurrences, this can be |
| // confusing for non-standard parsers, and the serialized representation is |
| // larger than necessary. |
| // |
| // It follows that there are two ways to write a length-delimited field: |
| // |
| // 1. Specify its parent in `RegisterParentForAdd()`. |
| // Write the whole field in `AddField()` action. |
| // |
| // 2. Specify the field itself in `RegisterParentForAdd()`. |
| // Write its contents only in `AddField()` action. |
| // |
| // If the field is not present in the base message (or removed), and |
| // `AddField()` is called once, then both ways have the same effect. |
| // The first way is more efficient, because the length of field contents |
| // is usually known in advance, in which case the contents can be written |
| // directly instead of gathering them separately to compute their length |
| // when they are complete. |
| // |
| // Otherwise, the first way writes a separate field, which may undergo |
| // implicit merging during parsing, while the second way merges it in the |
| // serialized message. |
| template < |
| typename Action, |
| std::enable_if_t<std::is_invocable_v<Action&&, SerializedMessageWriter&>, |
| int> = 0> |
| void AddField(ParentForAdd parent_for_add, Action&& action); |
| |
| // Removes from the base message the field identified by `parent_for_remove`, |
| // which was returned by `Builder::RegisterFieldForRemove()`. |
| // |
| // If the field occurs multiple times, all its occurrences are removed. |
| // |
| // If `RemoveField()` is called multiple times with the same |
| // `parent_for_remove`, the effect is the same as a single call. |
| // |
| // This does not apply to fields added with `AddField()`, only to fields from |
| // the base message. |
| void RemoveField(FieldForRemove parent_for_remove); |
| |
| // Writes a serialized message based on the serialized message in `base_src` |
| // (empty by default), adding fields specified by `Session::AddField()`, |
| // and removing fields specified by `Session::RemoveField()`. |
| // |
| // The `Session` is left empty. |
| template < |
| typename BaseSrc, typename Dest, |
| std::enable_if_t< |
| std::conjunction_v<IsSource<BaseSrc>, IsDestination<Dest>>, int> = 0> |
| absl::Status WriteMessage(const SerializedMessageAssembler& message_assembler, |
| BaseSrc&& base_src, Dest&& dest) &&; |
| template <typename Dest, |
| std::enable_if_t<IsDestination<Dest>::value, int> = 0> |
| absl::Status WriteMessage(const SerializedMessageAssembler& message_assembler, |
| Dest&& dest) &&; |
| |
| private: |
| template < |
| typename Dest, |
| std::enable_if_t<TargetRefSupportsDependency<Writer*, Dest>::value, int>> |
| static Dest&& MakeWriter(Dest&& dest) { |
| return std::forward<Dest>(dest); |
| } |
| static StringWriter<> MakeWriter(std::string& dest) { |
| return StringWriter(&dest); |
| } |
| static ChainWriter<> MakeWriter(Chain& dest) { return ChainWriter(&dest); } |
| static CordWriter<> MakeWriter(absl::Cord& dest) { return CordWriter(&dest); } |
| |
| template <typename BaseSrc, typename Dest> |
| absl::Status WriteMessageInternal( |
| const SerializedMessageAssembler& message_assembler, BaseSrc&& base_src, |
| Dest&& dest) &&; |
| template <typename Dest> |
| absl::Status WriteMessageInternal( |
| const SerializedMessageAssembler& message_assembler, Dest&& dest) &&; |
| |
| // Indexed by `ParentForAdd`. |
| std::vector<FieldValues> fields_to_add_; |
| // Indexed by `FieldForRemove`. |
| absl::InlinedVector<bool, 8> fields_to_remove_; |
| }; |
| |
| // Implementation details follow. |
| |
| template <typename Action, |
| std::enable_if_t< |
| std::is_invocable_v<Action&&, SerializedMessageWriter&>, int>> |
| inline void SerializedMessageAssembler::Session::AddField( |
| ParentForAdd parent_for_add, Action&& action) { |
| if (ABSL_PREDICT_FALSE(parent_for_add.raw >= fields_to_add_.size())) { |
| fields_to_add_.resize(parent_for_add.raw + 1); |
| } |
| fields_to_add_[parent_for_add.raw].emplace_back(std::forward<Action>(action)); |
| } |
| |
| inline void SerializedMessageAssembler::Session::RemoveField( |
| FieldForRemove parent_for_remove) { |
| if (ABSL_PREDICT_FALSE(parent_for_remove.raw >= fields_to_remove_.size())) { |
| fields_to_remove_.resize(parent_for_remove.raw + 1); |
| } |
| fields_to_remove_[parent_for_remove.raw] = true; |
| } |
| |
| template <typename BaseSrc, typename Dest, |
| std::enable_if_t< |
| std::conjunction_v< |
| SerializedMessageAssembler::Session::IsSource<BaseSrc>, |
| SerializedMessageAssembler::Session::IsDestination<Dest>>, |
| int>> |
| absl::Status SerializedMessageAssembler::Session::WriteMessage( |
| const SerializedMessageAssembler& message_assembler, BaseSrc&& base_src, |
| Dest&& dest) && { |
| return std::move(*this).WriteMessageInternal( |
| message_assembler, std::forward<BaseSrc>(base_src), |
| MakeWriter(std::forward<Dest>(dest))); |
| } |
| |
| template < |
| typename Dest, |
| std::enable_if_t< |
| SerializedMessageAssembler::Session::IsDestination<Dest>::value, int>> |
| absl::Status SerializedMessageAssembler::Session::WriteMessage( |
| const SerializedMessageAssembler& message_assembler, Dest&& dest) && { |
| return std::move(*this).WriteMessageInternal( |
| message_assembler, MakeWriter(std::forward<Dest>(dest))); |
| } |
| |
| template <typename BaseSrc, typename Dest> |
| inline absl::Status SerializedMessageAssembler::Session::WriteMessageInternal( |
| const SerializedMessageAssembler& message_assembler, BaseSrc&& base_src, |
| Dest&& dest) && { |
| DependencyRef<Writer*, Dest> dest_dep(std::forward<Dest>(dest)); |
| SerializedMessageWriter message_writer(dest_dep.get()); |
| |
| absl::Status status = RewriteFields( |
| absl::MakeSpan(fields_to_add_), absl::MakeSpan(fields_to_remove_), |
| message_assembler.root_, std::forward<BaseSrc>(base_src), message_writer); |
| |
| // Each element of `fields_to_add_` is already cleared. |
| fields_to_remove_.clear(); |
| if (dest_dep.IsOwning()) { |
| if (ABSL_PREDICT_FALSE(!dest_dep->Close())) { |
| status.Update(dest_dep->status()); |
| } |
| } |
| return status; |
| } |
| |
| template <typename Dest> |
| inline absl::Status SerializedMessageAssembler::Session::WriteMessageInternal( |
| const SerializedMessageAssembler& message_assembler, Dest&& dest) && { |
| DependencyRef<Writer*, Dest> dest_dep(std::forward<Dest>(dest)); |
| SerializedMessageWriter message_writer(dest_dep.get()); |
| |
| absl::Status status = |
| WriteNewFields(absl::MakeSpan(fields_to_add_), message_assembler.root_, |
| SubmessageSet(message_assembler.root_.submessages.size()), |
| message_writer); |
| |
| // Each element of `fields_to_add_` is already cleared. |
| fields_to_remove_.clear(); |
| if (dest_dep.IsOwning()) { |
| if (ABSL_PREDICT_FALSE(!dest_dep->Close())) { |
| status.Update(dest_dep->status()); |
| } |
| } |
| return status; |
| } |
| |
| template <typename Src> |
| absl::Status SerializedMessageAssembler::RewriteFields( |
| absl::Span<FieldValues> fields_to_add, |
| absl::Span<const bool> fields_to_remove, const RegisteredMessage& message, |
| Src&& src, SerializedMessageWriter& message_writer) { |
| SubmessageSet submessages_rewritten(message.submessages.size()); |
| |
| // Rewrite fields which are present in the base message. |
| if (absl::Status status = |
| SerializedMessageReader< |
| const absl::Span<FieldValues>, const absl::Span<const bool>, |
| const absl::Span<bool>, SerializedMessageWriter>( |
| std::cref(message.handlers), CopyingHandler()) |
| .ReadMessage( |
| std::forward<Src>(src), fields_to_add, fields_to_remove, |
| absl::MakeSpan(submessages_rewritten), message_writer); |
| ABSL_PREDICT_FALSE(!status.ok())) { |
| return status; |
| } |
| |
| // Write new fields which were not present in the base message. |
| return WriteNewFields(fields_to_add, message, submessages_rewritten, |
| message_writer); |
| } |
| |
| extern template absl::Status SerializedMessageAssembler::RewriteFields( |
| absl::Span<FieldValues> fields_to_add, |
| absl::Span<const bool> fields_to_remove, const RegisteredMessage& message, |
| ReaderSpan<>&& src, SerializedMessageWriter& message_writer); |
| extern template absl::Status SerializedMessageAssembler::RewriteFields( |
| absl::Span<FieldValues> fields_to_add, |
| absl::Span<const bool> fields_to_remove, const RegisteredMessage& message, |
| CordIteratorSpan&& src, SerializedMessageWriter& message_writer); |
| extern template absl::Status SerializedMessageAssembler::RewriteFields( |
| absl::Span<FieldValues> fields_to_add, |
| absl::Span<const bool> fields_to_remove, const RegisteredMessage& message, |
| absl::string_view&& src, SerializedMessageWriter& message_writer); |
| |
| } // namespace riegeli |
| |
| #endif // RIEGELI_MESSAGES_SERIALIZED_MESSAGE_ASSEMBLER_H_ |