diff --git a/Firestore/Example/Tests/Local/FSTLevelDBMigrationsTests.mm b/Firestore/Example/Tests/Local/FSTLevelDBMigrationsTests.mm index 0f637853730..5f25935ea41 100644 --- a/Firestore/Example/Tests/Local/FSTLevelDBMigrationsTests.mm +++ b/Firestore/Example/Tests/Local/FSTLevelDBMigrationsTests.mm @@ -22,11 +22,11 @@ #import "Firestore/Protos/objc/firestore/local/Target.pbobjc.h" #import "Firestore/Source/Local/FSTLevelDB.h" -#import "Firestore/Source/Local/FSTLevelDBMigrations.h" #import "Firestore/Source/Local/FSTLevelDBMutationQueue.h" #import "Firestore/Source/Local/FSTLevelDBQueryCache.h" #include "Firestore/core/src/firebase/firestore/local/leveldb_key.h" +#include "Firestore/core/src/firebase/firestore/local/leveldb_migrations.h" #include "Firestore/core/src/firebase/firestore/util/ordered_code.h" #include "Firestore/core/src/firebase/firestore/util/status.h" #include "Firestore/core/test/firebase/firestore/testutil/testutil.h" @@ -39,6 +39,7 @@ using firebase::firestore::FirestoreErrorCode; using firebase::firestore::local::LevelDbDocumentTargetKey; +using firebase::firestore::local::LevelDbMigrations; using firebase::firestore::local::LevelDbMutationKey; using firebase::firestore::local::LevelDbMutationQueueKey; using firebase::firestore::local::LevelDbQueryTargetKey; @@ -54,6 +55,8 @@ using leveldb::Options; using leveldb::Status; +using SchemaVersion = LevelDbMigrations::SchemaVersion; + @interface FSTLevelDBMigrationsTests : XCTestCase @end @@ -80,7 +83,7 @@ - (void)tearDown { - (void)testAddsTargetGlobal { FSTPBTargetGlobal *metadata = [FSTLevelDBQueryCache readTargetMetadataFromDB:_db.get()]; XCTAssertNil(metadata, @"Not expecting metadata yet, we should have an empty db"); - [FSTLevelDBMigrations runMigrationsWithDatabase:_db.get()]; + LevelDbMigrations::RunMigrations(_db.get()); metadata = [FSTLevelDBQueryCache readTargetMetadataFromDB:_db.get()]; XCTAssertNotNil(metadata, @"Migrations should have added the metadata"); @@ -89,18 +92,16 @@ - (void)testAddsTargetGlobal { - (void)testSetsVersionNumber { { LevelDbTransaction transaction(_db.get(), "testSetsVersionNumber before"); - FSTLevelDBSchemaVersion initial = - [FSTLevelDBMigrations schemaVersionWithTransaction:&transaction]; + SchemaVersion initial = LevelDbMigrations::ReadSchemaVersion(&transaction); XCTAssertEqual(0, initial, "No version should be equivalent to 0"); } { // Pick an arbitrary high migration number and migrate to it. - [FSTLevelDBMigrations runMigrationsWithDatabase:_db.get()]; + LevelDbMigrations::RunMigrations(_db.get()); LevelDbTransaction transaction(_db.get(), "testSetsVersionNumber after"); - FSTLevelDBSchemaVersion actual = - [FSTLevelDBMigrations schemaVersionWithTransaction:&transaction]; + SchemaVersion actual = LevelDbMigrations::ReadSchemaVersion(&transaction); XCTAssertGreaterThan(actual, 0, @"Expected to migrate to a schema version > 0"); } } @@ -143,7 +144,7 @@ - (void)testDropsTheQueryCache { LevelDbMutationKey::Key(userID, batchID), }; - [FSTLevelDBMigrations runMigrationsWithDatabase:_db.get() upToVersion:2]; + LevelDbMigrations::RunMigrations(_db.get(), 2); { // Setup some targets to be counted in the migration. LevelDbTransaction transaction(_db.get(), "testDropsTheQueryCache setup"); @@ -156,7 +157,7 @@ - (void)testDropsTheQueryCache { transaction.Commit(); } - [FSTLevelDBMigrations runMigrationsWithDatabase:_db.get() upToVersion:3]; + LevelDbMigrations::RunMigrations(_db.get(), 3); { LevelDbTransaction transaction(_db.get(), "testDropsTheQueryCache"); for (const std::string &key : targetKeys) { @@ -173,7 +174,7 @@ - (void)testDropsTheQueryCache { } - (void)testDropsTheQueryCacheWithThousandsOfEntries { - [FSTLevelDBMigrations runMigrationsWithDatabase:_db.get() upToVersion:2]; + LevelDbMigrations::RunMigrations(_db.get(), 2); { // Setup some targets to be destroyed. LevelDbTransaction transaction(_db.get(), "testDropsTheQueryCacheWithThousandsOfEntries setup"); @@ -183,7 +184,7 @@ - (void)testDropsTheQueryCacheWithThousandsOfEntries { transaction.Commit(); } - [FSTLevelDBMigrations runMigrationsWithDatabase:_db.get() upToVersion:3]; + LevelDbMigrations::RunMigrations(_db.get(), 3); { LevelDbTransaction transaction(_db.get(), "Verify"); std::string prefix = LevelDbTargetKey::KeyPrefix(); diff --git a/Firestore/Source/Local/FSTLevelDB.mm b/Firestore/Source/Local/FSTLevelDB.mm index ef0e88e14a6..7500819b094 100644 --- a/Firestore/Source/Local/FSTLevelDB.mm +++ b/Firestore/Source/Local/FSTLevelDB.mm @@ -22,7 +22,6 @@ #import "FIRFirestoreErrors.h" #import "Firestore/Source/Core/FSTListenSequence.h" #import "Firestore/Source/Local/FSTLRUGarbageCollector.h" -#import "Firestore/Source/Local/FSTLevelDBMigrations.h" #import "Firestore/Source/Local/FSTLevelDBMutationQueue.h" #import "Firestore/Source/Local/FSTLevelDBQueryCache.h" #import "Firestore/Source/Local/FSTLevelDBRemoteDocumentCache.h" @@ -33,6 +32,7 @@ #include "Firestore/core/src/firebase/firestore/auth/user.h" #include "Firestore/core/src/firebase/firestore/core/database_info.h" #include "Firestore/core/src/firebase/firestore/local/leveldb_key.h" +#include "Firestore/core/src/firebase/firestore/local/leveldb_migrations.h" #include "Firestore/core/src/firebase/firestore/local/leveldb_transaction.h" #include "Firestore/core/src/firebase/firestore/local/leveldb_util.h" #include "Firestore/core/src/firebase/firestore/model/database_id.h" @@ -58,6 +58,7 @@ using firebase::firestore::local::ConvertStatus; using firebase::firestore::local::LevelDbDocumentMutationKey; using firebase::firestore::local::LevelDbDocumentTargetKey; +using firebase::firestore::local::LevelDbMigrations; using firebase::firestore::local::LevelDbMutationKey; using firebase::firestore::local::LevelDbTransaction; using firebase::firestore::model::DatabaseId; @@ -352,7 +353,7 @@ - (Status)start { } _ptr = std::move(database).ValueOrDie(); - [FSTLevelDBMigrations runMigrationsWithDatabase:_ptr.get()]; + LevelDbMigrations::RunMigrations(_ptr.get()); LevelDbTransaction transaction(_ptr.get(), "Start LevelDB"); _users = [FSTLevelDB collectUserSet:&transaction]; transaction.Commit(); diff --git a/Firestore/Source/Local/FSTLevelDBMigrations.h b/Firestore/Source/Local/FSTLevelDBMigrations.h deleted file mode 100644 index 0987da53c61..00000000000 --- a/Firestore/Source/Local/FSTLevelDBMigrations.h +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2018 Google - * - * 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. - */ - -#import - -#include - -#include "Firestore/core/src/firebase/firestore/local/leveldb_transaction.h" -#include "leveldb/db.h" - -NS_ASSUME_NONNULL_BEGIN - -typedef int32_t FSTLevelDBSchemaVersion; - -@interface FSTLevelDBMigrations : NSObject - -/** - * Returns the current version of the schema for the given database - */ -+ (FSTLevelDBSchemaVersion)schemaVersionWithTransaction: - (firebase::firestore::local::LevelDbTransaction *)transaction; - -/** - * Runs any migrations needed to bring the given database up to the current schema version - */ -+ (void)runMigrationsWithDatabase:(leveldb::DB *)database; - -/** - * Runs any migrations needed to bring the given database up to the given schema version - */ -+ (void)runMigrationsWithDatabase:(leveldb::DB *)database - upToVersion:(FSTLevelDBSchemaVersion)version; - -@end - -NS_ASSUME_NONNULL_END diff --git a/Firestore/core/src/firebase/firestore/local/CMakeLists.txt b/Firestore/core/src/firebase/firestore/local/CMakeLists.txt index 7dfe7af659d..a36aa928d01 100644 --- a/Firestore/core/src/firebase/firestore/local/CMakeLists.txt +++ b/Firestore/core/src/firebase/firestore/local/CMakeLists.txt @@ -16,16 +16,23 @@ if(HAVE_LEVELDB) cc_library( firebase_firestore_local_persistence_leveldb SOURCES - leveldb_key.h leveldb_key.cc - leveldb_transaction.h + leveldb_key.h + leveldb_migrations.cc + leveldb_migrations.h leveldb_transaction.cc - leveldb_util.h + leveldb_transaction.h leveldb_util.cc + leveldb_util.h DEPENDS + # TODO(b/111328563) Force nanopb first to work around ODR violations + protobuf-nanopb + LevelDB::LevelDB absl_strings firebase_firestore_model + firebase_firestore_nanopb + firebase_firestore_protos_nanopb firebase_firestore_util EXCLUDE_FROM_ALL ) diff --git a/Firestore/Source/Local/FSTLevelDBMigrations.mm b/Firestore/core/src/firebase/firestore/local/leveldb_migrations.cc similarity index 52% rename from Firestore/Source/Local/FSTLevelDBMigrations.mm rename to Firestore/core/src/firebase/firestore/local/leveldb_migrations.cc index ce5139473a1..dc4d1594e56 100644 --- a/Firestore/Source/Local/FSTLevelDBMigrations.mm +++ b/Firestore/core/src/firebase/firestore/local/leveldb_migrations.cc @@ -14,69 +14,69 @@ * limitations under the License. */ -#import "Firestore/Source/Local/FSTLevelDBMigrations.h" +#include "Firestore/core/src/firebase/firestore/local/leveldb_migrations.h" #include +#include -#import "Firestore/Protos/objc/firestore/local/Target.pbobjc.h" -#import "Firestore/Source/Local/FSTLevelDBQueryCache.h" - +#include "Firestore/Protos/nanopb/firestore/local/target.nanopb.h" #include "Firestore/core/src/firebase/firestore/local/leveldb_key.h" -#include "Firestore/core/src/firebase/firestore/util/hard_assert.h" -#include "absl/base/macros.h" -#include "absl/memory/memory.h" +#include "Firestore/core/src/firebase/firestore/nanopb/writer.h" #include "absl/strings/match.h" -#include "leveldb/write_batch.h" -NS_ASSUME_NONNULL_BEGIN +namespace firebase { +namespace firestore { +namespace local { + +using leveldb::Iterator; +using leveldb::Slice; +using leveldb::Status; +using leveldb::WriteOptions; +using nanopb::Writer; + +namespace { /** * Schema version for the iOS client. * - * Note that tables aren't a concept in LevelDB. They exist in our schema as just prefixes on keys. - * This means tables don't need to be created but they also can't easily be dropped and re-created. + * Note that tables aren't a concept in LevelDB. They exist in our schema as + * just prefixes on keys. This means tables don't need to be created but they + * also can't easily be dropped and re-created. * * Migrations: - * * Migration 1 used to ensure the target_global row existed, without clearing it. No longer - * required because migration 3 unconditionally clears it. - * * Migration 2 used to ensure that the target_global row had a correct count of targets. No - * longer required because migration 3 deletes them all. - * * Migration 3 deletes the entire query cache to deal with cache corruption related to - * limbo resolution. Addresses https://github.com/firebase/firebase-ios-sdk/issues/1548. + * * Migration 1 used to ensure the target_global row existed, without + * clearing it. No longer required because migration 3 unconditionally + * clears it. + * * Migration 2 used to ensure that the target_global row had a correct count + * of targets. No longer required because migration 3 deletes them all. + * * Migration 3 deletes the entire query cache to deal with cache corruption + * related to limbo resolution. Addresses + * https://github.com/firebase/firebase-ios-sdk/issues/1548. */ -static FSTLevelDBSchemaVersion kSchemaVersion = 3; - -using firebase::firestore::local::LevelDbDocumentTargetKey; -using firebase::firestore::local::LevelDbQueryTargetKey; -using firebase::firestore::local::LevelDbTargetDocumentKey; -using firebase::firestore::local::LevelDbTargetGlobalKey; -using firebase::firestore::local::LevelDbTargetKey; -using firebase::firestore::local::LevelDbTransaction; -using firebase::firestore::local::LevelDbVersionKey; -using leveldb::Iterator; -using leveldb::Status; -using leveldb::Slice; -using leveldb::WriteOptions; +const LevelDbMigrations::SchemaVersion kSchemaVersion = 3; /** - * Save the given version number as the current version of the schema of the database. + * Save the given version number as the current version of the schema of the + * database. * @param version The version to save * @param transaction The transaction in which to save the new version number */ -static void SaveVersion(FSTLevelDBSchemaVersion version, LevelDbTransaction *transaction) { +void SaveVersion(LevelDbMigrations::SchemaVersion version, + LevelDbTransaction* transaction) { std::string key = LevelDbVersionKey::Key(); std::string version_string = std::to_string(version); transaction->Put(key, version_string); } -static void DeleteEverythingWithPrefix(const std::string &prefix, leveldb::DB *db) { +void DeleteEverythingWithPrefix(const std::string& prefix, leveldb::DB* db) { bool more_deletes = true; while (more_deletes) { LevelDbTransaction transaction(db, "Delete everything with prefix"); auto it = transaction.NewIterator(); more_deletes = false; - for (it->Seek(prefix); it->Valid() && absl::StartsWith(it->key(), prefix); it->Next()) { + for (it->Seek(prefix); it->Valid() && absl::StartsWith(it->key(), prefix); + it->Next()) { if (transaction.changed_keys() >= 1000) { more_deletes = true; break; @@ -89,7 +89,7 @@ static void DeleteEverythingWithPrefix(const std::string &prefix, leveldb::DB *d } /** Migration 3. */ -static void ClearQueryCache(leveldb::DB *db) { +void ClearQueryCache(leveldb::DB* db) { DeleteEverythingWithPrefix(LevelDbTargetKey::KeyPrefix(), db); DeleteEverythingWithPrefix(LevelDbDocumentTargetKey::KeyPrefix(), db); DeleteEverythingWithPrefix(LevelDbTargetDocumentKey::KeyPrefix(), db); @@ -98,16 +98,22 @@ static void ClearQueryCache(leveldb::DB *db) { LevelDbTransaction transaction(db, "Drop query cache"); // Reset the target global entry too (to reset the target count). - transaction.Put(LevelDbTargetGlobalKey::Key(), [FSTPBTargetGlobal message]); + firestore_client_TargetGlobal target_global{}; + + std::string bytes; + Writer writer = Writer::Wrap(&bytes); + writer.WriteNanopbMessage(firestore_client_TargetGlobal_fields, + &target_global); + transaction.Put(LevelDbTargetGlobalKey::Key(), std::move(bytes)); SaveVersion(3, &transaction); transaction.Commit(); } -@implementation FSTLevelDBMigrations +} // namespace -+ (FSTLevelDBSchemaVersion)schemaVersionWithTransaction: - (firebase::firestore::local::LevelDbTransaction *)transaction { +LevelDbMigrations::SchemaVersion LevelDbMigrations::ReadSchemaVersion( + LevelDbTransaction* transaction) { std::string key = LevelDbVersionKey::Key(); std::string version_string; Status status = transaction->Get(key, &version_string); @@ -118,22 +124,23 @@ + (FSTLevelDBSchemaVersion)schemaVersionWithTransaction: } } -+ (void)runMigrationsWithDatabase:(leveldb::DB *)database { - [self runMigrationsWithDatabase:database upToVersion:kSchemaVersion]; +void LevelDbMigrations::RunMigrations(leveldb::DB* db) { + RunMigrations(db, kSchemaVersion); } -+ (void)runMigrationsWithDatabase:(leveldb::DB *)database - upToVersion:(FSTLevelDBSchemaVersion)toVersion { - LevelDbTransaction transaction{database, "Read schema version"}; - FSTLevelDBSchemaVersion fromVersion = [self schemaVersionWithTransaction:&transaction]; +void LevelDbMigrations::RunMigrations(leveldb::DB* db, + SchemaVersion to_version) { + LevelDbTransaction transaction{db, "Read schema version"}; + SchemaVersion from_version = ReadSchemaVersion(&transaction); - // This must run unconditionally because schema migrations were added to iOS after the first - // release. There may be clients that have never run any migrations that have existing targets. - if (fromVersion < 3 && toVersion >= 3) { - ClearQueryCache(database); + // This must run unconditionally because schema migrations were added to iOS + // after the first release. There may be clients that have never run any + // migrations that have existing targets. + if (from_version < 3 && to_version >= 3) { + ClearQueryCache(db); } } -@end - -NS_ASSUME_NONNULL_END +} // namespace local +} // namespace firestore +} // namespace firebase diff --git a/Firestore/core/src/firebase/firestore/local/leveldb_migrations.h b/Firestore/core/src/firebase/firestore/local/leveldb_migrations.h new file mode 100644 index 00000000000..5f7bd59be70 --- /dev/null +++ b/Firestore/core/src/firebase/firestore/local/leveldb_migrations.h @@ -0,0 +1,56 @@ +/* + * Copyright 2018 Google + * + * 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 FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_LOCAL_LEVELDB_MIGRATIONS_H_ +#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_LOCAL_LEVELDB_MIGRATIONS_H_ + +#include + +#include "Firestore/core/src/firebase/firestore/local/leveldb_transaction.h" +#include "Firestore/core/src/firebase/firestore/local/local_serializer.h" +#include "leveldb/db.h" + +namespace firebase { +namespace firestore { +namespace local { + +class LevelDbMigrations { + public: + using SchemaVersion = int32_t; + + /** + * Returns the current version of the schema for the given database + */ + static SchemaVersion ReadSchemaVersion(LevelDbTransaction* transaction); + + /** + * Runs any migrations needed to bring the given database up to the current + * schema version + */ + static void RunMigrations(leveldb::DB* db); + + /** + * Runs any migrations needed to bring the given database up to the given + * schema version + */ + static void RunMigrations(leveldb::DB* db, SchemaVersion version); +}; + +} // namespace local +} // namespace firestore +} // namespace firebase + +#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_LOCAL_LEVELDB_MIGRATIONS_H_ diff --git a/Firestore/core/src/firebase/firestore/local/leveldb_transaction.cc b/Firestore/core/src/firebase/firestore/local/leveldb_transaction.cc index 6ce09ccb03e..b1d9f5c1cf6 100644 --- a/Firestore/core/src/firebase/firestore/local/leveldb_transaction.cc +++ b/Firestore/core/src/firebase/firestore/local/leveldb_transaction.cc @@ -162,11 +162,9 @@ const WriteOptions& LevelDbTransaction::DefaultWriteOptions() { return options; } -void LevelDbTransaction::Put(absl::string_view key, absl::string_view value) { - std::string key_string(key); - std::string value_string(value); - mutations_[key_string] = value_string; - deletions_.erase(key_string); +void LevelDbTransaction::Put(std::string key, std::string value) { + deletions_.erase(key); + mutations_.emplace(std::make_pair(std::move(key), std::move(value))); version_++; } diff --git a/Firestore/core/src/firebase/firestore/local/leveldb_transaction.h b/Firestore/core/src/firebase/firestore/local/leveldb_transaction.h index b98d831ab49..3dc06fea82b 100644 --- a/Firestore/core/src/firebase/firestore/local/leveldb_transaction.h +++ b/Firestore/core/src/firebase/firestore/local/leveldb_transaction.h @@ -173,7 +173,7 @@ class LevelDbTransaction { * Schedules the row identified by `key` to be set to `value` when this * transaction commits. */ - void Put(absl::string_view key, absl::string_view value); + void Put(std::string key, std::string value); /** * Sets the contents of `value` to the latest known value for the given key, diff --git a/Firestore/core/src/firebase/firestore/nanopb/writer.cc b/Firestore/core/src/firebase/firestore/nanopb/writer.cc index c9771412c41..e4ba31013fb 100644 --- a/Firestore/core/src/firebase/firestore/nanopb/writer.cc +++ b/Firestore/core/src/firebase/firestore/nanopb/writer.cc @@ -27,11 +27,23 @@ using std::int64_t; using std::int8_t; using std::uint64_t; -Writer Writer::Wrap(std::vector* out_bytes) { - // TODO(rsgowman): find a better home for this constant. - // A document is defined to have a max size of 1MiB - 4 bytes. - static const size_t kMaxDocumentSize = 1 * 1024 * 1024 - 4; +namespace { + +// TODO(rsgowman): find a better home for this constant. +// A document is defined to have a max size of 1MiB - 4 bytes. +const size_t kMaxDocumentSize = 1 * 1024 * 1024 - 4; +/** + * Creates a pb_ostream_t to the specified STL container. Note that this pointer + * must remain valid for the lifetime of the stream. + * + * (This is roughly equivalent to the nanopb function pb_ostream_from_buffer().) + * + * @tparm Container an STL container whose value_type is a char type. + * @param out_container where the output should be serialized to. + */ +template +pb_ostream_t WrapContainer(Container* out_container) { // Construct a nanopb output stream. // // Set the max_size to be the max document size (as an upper bound; one would @@ -40,18 +52,26 @@ Writer Writer::Wrap(std::vector* out_bytes) { // bytes_written is (always) initialized to 0. (NB: nanopb does not know or // care about the underlying output vector, so where we are in the vector // itself is irrelevant. i.e. don't use out_bytes->size()) - pb_ostream_t raw_stream = { - /*callback=*/[](pb_ostream_t* stream, const pb_byte_t* buf, - size_t count) -> bool { - auto* out_bytes = static_cast*>(stream->state); - out_bytes->insert(out_bytes->end(), buf, buf + count); - return true; - }, - /*state=*/out_bytes, - /*max_size=*/kMaxDocumentSize, - /*bytes_written=*/0, - /*errmsg=*/nullptr}; - return Writer(raw_stream); + return {/*callback=*/[](pb_ostream_t* stream, const pb_byte_t* buf, + size_t count) -> bool { + auto* output = static_cast(stream->state); + output->insert(output->end(), buf, buf + count); + return true; + }, + /*state=*/out_container, + /*max_size=*/kMaxDocumentSize, + /*bytes_written=*/0, + /*errmsg=*/nullptr}; +} + +} // namespace + +Writer Writer::Wrap(std::vector* out_bytes) { + return Writer{WrapContainer(out_bytes)}; +} + +Writer Writer::Wrap(std::string* out_string) { + return Writer{WrapContainer(out_string)}; } void Writer::WriteTag(Tag tag) { diff --git a/Firestore/core/src/firebase/firestore/nanopb/writer.h b/Firestore/core/src/firebase/firestore/nanopb/writer.h index 6c47258adfb..0cdc579a60f 100644 --- a/Firestore/core/src/firebase/firestore/nanopb/writer.h +++ b/Firestore/core/src/firebase/firestore/nanopb/writer.h @@ -48,6 +48,17 @@ class Writer { */ static Writer Wrap(std::vector* out_bytes); + /** + * Creates an output stream that writes to the specified string. Note that + * this string pointer must remain valid for the lifetime of this Writer. + * + * (This is roughly equivalent to the nanopb function + * pb_ostream_from_buffer()) + * + * @param out_string where the output should be serialized to. + */ + static Writer Wrap(std::string* out_string); + /** * Creates a non-writing output stream used to calculate the size of * the serialized output.