From b271a6e25144be8cf872d028bb82336b5da2074c Mon Sep 17 00:00:00 2001 From: rsgowman Date: Thu, 21 Jun 2018 13:59:56 -0400 Subject: Initial creation of the local serializer. (#1415) Added a single, basic test as a motivator. --- .../src/firebase/firestore/local/CMakeLists.txt | 6 + .../firebase/firestore/local/local_serializer.cc | 185 +++++++++++++++++++++ .../firebase/firestore/local/local_serializer.h | 109 ++++++++++++ .../src/firebase/firestore/remote/serializer.cc | 55 ++++-- .../src/firebase/firestore/remote/serializer.h | 17 +- 5 files changed, 353 insertions(+), 19 deletions(-) create mode 100644 Firestore/core/src/firebase/firestore/local/local_serializer.cc create mode 100644 Firestore/core/src/firebase/firestore/local/local_serializer.h (limited to 'Firestore/core/src/firebase') diff --git a/Firestore/core/src/firebase/firestore/local/CMakeLists.txt b/Firestore/core/src/firebase/firestore/local/CMakeLists.txt index 4d39c3d..3e009bb 100644 --- a/Firestore/core/src/firebase/firestore/local/CMakeLists.txt +++ b/Firestore/core/src/firebase/firestore/local/CMakeLists.txt @@ -19,9 +19,15 @@ cc_library( leveldb_key.cc leveldb_transaction.h leveldb_transaction.cc + local_serializer.h + local_serializer.cc DEPENDS LevelDB::LevelDB absl_strings firebase_firestore_model + firebase_firestore_nanopb + firebase_firestore_protos_nanopb + firebase_firestore_remote firebase_firestore_util + nanopb ) diff --git a/Firestore/core/src/firebase/firestore/local/local_serializer.cc b/Firestore/core/src/firebase/firestore/local/local_serializer.cc new file mode 100644 index 0000000..fea99fb --- /dev/null +++ b/Firestore/core/src/firebase/firestore/local/local_serializer.cc @@ -0,0 +1,185 @@ +/* + * 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. + */ + +#include "Firestore/core/src/firebase/firestore/local/local_serializer.h" + +#include +#include + +#include "Firestore/Protos/nanopb/firestore/local/maybe_document.nanopb.h" +#include "Firestore/Protos/nanopb/google/firestore/v1beta1/document.nanopb.h" +#include "Firestore/core/src/firebase/firestore/model/field_value.h" +#include "Firestore/core/src/firebase/firestore/model/no_document.h" +#include "Firestore/core/src/firebase/firestore/nanopb/tag.h" +#include "Firestore/core/src/firebase/firestore/util/hard_assert.h" + +namespace firebase { +namespace firestore { +namespace local { + +using firebase::firestore::model::ObjectValue; +using firebase::firestore::nanopb::Reader; +using firebase::firestore::nanopb::Tag; +using firebase::firestore::nanopb::Writer; +using firebase::firestore::util::Status; + +Status LocalSerializer::EncodeMaybeDocument( + const model::MaybeDocument& document, + std::vector* out_bytes) const { + Writer writer = Writer::Wrap(out_bytes); + EncodeMaybeDocument(&writer, document); + return writer.status(); +} + +void LocalSerializer::EncodeMaybeDocument( + Writer* writer, const model::MaybeDocument& maybe_doc) const { + switch (maybe_doc.type()) { + case model::MaybeDocument::Type::Document: + writer->WriteTag( + {PB_WT_STRING, firestore_client_MaybeDocument_document_tag}); + writer->WriteNestedMessage([&](Writer* writer) { + EncodeDocument(writer, static_cast(maybe_doc)); + }); + return; + + case model::MaybeDocument::Type::NoDocument: + // TODO(rsgowman) + abort(); + + case model::MaybeDocument::Type::Unknown: + // TODO(rsgowman) + abort(); + } + + UNREACHABLE(); +} + +std::unique_ptr LocalSerializer::DecodeMaybeDocument( + Reader* reader) const { + if (!reader->status().ok()) return nullptr; + + // Initialize MaybeDocument fields to their default values. (Due to the + // 'oneof' in MaybeDocument, only one of 'no_document' or 'document' should + // ever be set.) + std::unique_ptr no_document; + std::unique_ptr document; + + while (reader->bytes_left()) { + Tag tag = reader->ReadTag(); + if (!reader->status().ok()) return nullptr; + + // Ensure the tag matches the wire type + switch (tag.field_number) { + case firestore_client_MaybeDocument_document_tag: + case firestore_client_MaybeDocument_no_document_tag: + if (tag.wire_type != PB_WT_STRING) { + reader->set_status( + Status(FirestoreErrorCode::DataLoss, + "Input proto bytes cannot be parsed (mismatch between " + "the wiretype and the field number (tag))")); + return nullptr; + } + break; + + default: + // Unknown tag. According to the proto spec, we need to ignore these. No + // action required here, though we'll need to skip the relevant bytes + // below. + break; + } + + switch (tag.field_number) { + case firestore_client_MaybeDocument_document_tag: + // 'no_document' and 'document' are part of a oneof. The proto docs + // claim that if both are set on the wire, the last one wins. + no_document = nullptr; + + // TODO(rsgowman): If multiple '_document' values are found, we should + // merge them (rather than using the last one.) + document = reader->ReadNestedMessage>( + [&](Reader* reader) -> std::unique_ptr { + return rpc_serializer_.DecodeDocument(reader); + }); + break; + + case firestore_client_MaybeDocument_no_document_tag: + // 'no_document' and 'document' are part of a oneof. The proto docs + // claim that if both are set on the wire, the last one wins. + document = nullptr; + + // TODO(rsgowman): Parse the no_document field. + abort(); + + default: + // Unknown tag. According to the proto spec, we need to ignore these. + reader->SkipField(tag); + } + } + + if (no_document) { + return no_document; + } else if (document) { + return document; + } else { + reader->set_status(Status(FirestoreErrorCode::DataLoss, + "Invalid MaybeDocument message: Neither " + "'no_document' nor 'document' fields set.")); + return nullptr; + } +} + +void LocalSerializer::EncodeDocument(Writer* writer, + const model::Document& doc) const { + // Encode Document.name + writer->WriteTag({PB_WT_STRING, google_firestore_v1beta1_Document_name_tag}); + writer->WriteString(rpc_serializer_.EncodeKey(doc.key())); + + // Encode Document.fields (unless it's empty) + const ObjectValue& object_value = doc.data().object_value(); + if (!object_value.internal_value.empty()) { + rpc_serializer_.EncodeObjectMap( + writer, object_value.internal_value, + google_firestore_v1beta1_Document_fields_tag, + google_firestore_v1beta1_Document_FieldsEntry_key_tag, + google_firestore_v1beta1_Document_FieldsEntry_value_tag); + } + + // Encode Document.update_time + writer->WriteTag( + {PB_WT_STRING, google_firestore_v1beta1_Document_update_time_tag}); + writer->WriteNestedMessage([&](Writer* writer) { + rpc_serializer_.EncodeVersion(writer, doc.version()); + }); + + // Ignore Document.create_time. (We don't use this in our on-disk protos.) +} + +util::StatusOr> +LocalSerializer::DecodeMaybeDocument(const uint8_t* bytes, + size_t length) const { + Reader reader = Reader::Wrap(bytes, length); + std::unique_ptr maybe_doc = + DecodeMaybeDocument(&reader); + if (reader.status().ok()) { + return std::move(maybe_doc); + } else { + return reader.status(); + } +} + +} // namespace local +} // namespace firestore +} // namespace firebase diff --git a/Firestore/core/src/firebase/firestore/local/local_serializer.h b/Firestore/core/src/firebase/firestore/local/local_serializer.h new file mode 100644 index 0000000..c1b4761 --- /dev/null +++ b/Firestore/core/src/firebase/firestore/local/local_serializer.h @@ -0,0 +1,109 @@ +/* + * 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_LOCAL_SERIALIZER_H_ +#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_LOCAL_LOCAL_SERIALIZER_H_ + +#include +#include + +#include "Firestore/core/src/firebase/firestore/model/maybe_document.h" +#include "Firestore/core/src/firebase/firestore/nanopb/reader.h" +#include "Firestore/core/src/firebase/firestore/nanopb/writer.h" +#include "Firestore/core/src/firebase/firestore/remote/serializer.h" +#include "Firestore/core/src/firebase/firestore/util/status.h" + +namespace firebase { +namespace firestore { +namespace local { + +/** + * @brief Serializer for values stored in the LocalStore. + * + * Note that local::LocalSerializer currently delegates to the + * remote::Serializer (for the Firestore v1beta1 RPC protocol) to save + * implementation time and code duplication. We'll need to revisit this when the + * RPC protocol we use diverges from local storage. + */ +class LocalSerializer { + public: + explicit LocalSerializer(const remote::Serializer& rpc_serializer) + : rpc_serializer_(rpc_serializer) { + } + + /** + * @brief Encodes a MaybeDocument model to the equivalent bytes for local + * storage. + * + * @param document the model to convert. + * @param[out] out_bytes A buffer to place the output. The bytes will be + * appended to this vector. + * @return A Status, which if not ok(), indicates what went wrong. Note that + * errors during encoding generally indicate a serious/fatal error. + */ + // TODO(rsgowman): If we never support any output except to a vector, it may + // make sense to have LocalSerializer own the vector and provide an accessor + // rather than asking the user to create it first. + util::Status EncodeMaybeDocument(const model::MaybeDocument& maybe_doc, + std::vector* out_bytes) const; + + /** + * @brief Decodes bytes representing a MaybeDocument proto to the equivalent + * model. + * + * @param bytes The bytes to convert. It's assumed that exactly all of the + * bytes will be used by this conversion. + * @return The model equivalent of the bytes or a Status indicating what went + * wrong. + */ + util::StatusOr> DecodeMaybeDocument( + const uint8_t* bytes, size_t length) const; + + /** + * @brief Decodes bytes representing a MaybeDocument proto to the equivalent + * model. + * + * @param bytes The bytes to convert. It's assumed that exactly all of the + * bytes will be used by this conversion. + * @return The model equivalent of the bytes or a Status indicating what went + * wrong. + */ + util::StatusOr> DecodeMaybeDocument( + const std::vector& bytes) const { + return DecodeMaybeDocument(bytes.data(), bytes.size()); + } + + private: + void EncodeMaybeDocument(nanopb::Writer* writer, + const model::MaybeDocument& maybe_doc) const; + std::unique_ptr DecodeMaybeDocument( + nanopb::Reader* reader) const; + + /** + * Encodes a Document for local storage. This differs from the v1beta1 RPC + * serializer for Documents in that it preserves the updateTime, which is + * considered an output only value by the server. + */ + void EncodeDocument(nanopb::Writer* writer, const model::Document& doc) const; + + const remote::Serializer& rpc_serializer_; +}; + +} // namespace local +} // namespace firestore +} // namespace firebase + +#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_LOCAL_LOCAL_SERIALIZER_H_ diff --git a/Firestore/core/src/firebase/firestore/remote/serializer.cc b/Firestore/core/src/firebase/firestore/remote/serializer.cc index 19a068b..fc8b312 100644 --- a/Firestore/core/src/firebase/firestore/remote/serializer.cc +++ b/Firestore/core/src/firebase/firestore/remote/serializer.cc @@ -63,11 +63,11 @@ using firebase::firestore::util::StatusOr; namespace { void EncodeMapValue(Writer* writer, const ObjectValue& object_value); -void EncodeObjectMap(Writer* writer, - const ObjectValue::Map& object_value_map, - uint32_t map_tag, - uint32_t key_tag, - uint32_t value_tag); +void EncodeObjectMapImpl(Writer* writer, + const ObjectValue::Map& object_value_map, + uint32_t map_tag, + uint32_t key_tag, + uint32_t value_tag); ObjectValue::Map DecodeMapValue(Reader* reader); @@ -336,11 +336,11 @@ ObjectValue::Map::value_type DecodeDocumentFieldsEntry(Reader* reader) { google_firestore_v1beta1_Document_FieldsEntry_value_tag); } -void EncodeObjectMap(Writer* writer, - const ObjectValue::Map& object_value_map, - uint32_t map_tag, - uint32_t key_tag, - uint32_t value_tag) { +void EncodeObjectMapImpl(Writer* writer, + const ObjectValue::Map& object_value_map, + uint32_t map_tag, + uint32_t key_tag, + uint32_t value_tag) { // Write each FieldsEntry (i.e. key-value pair.) for (const auto& kv : object_value_map) { writer->WriteTag({PB_WT_STRING, map_tag}); @@ -351,10 +351,10 @@ void EncodeObjectMap(Writer* writer, } void EncodeMapValue(Writer* writer, const ObjectValue& object_value) { - EncodeObjectMap(writer, object_value.internal_value, - google_firestore_v1beta1_MapValue_fields_tag, - google_firestore_v1beta1_MapValue_FieldsEntry_key_tag, - google_firestore_v1beta1_MapValue_FieldsEntry_value_tag); + EncodeObjectMapImpl(writer, object_value.internal_value, + google_firestore_v1beta1_MapValue_fields_tag, + google_firestore_v1beta1_MapValue_FieldsEntry_key_tag, + google_firestore_v1beta1_MapValue_FieldsEntry_value_tag); } ObjectValue::Map DecodeMapValue(Reader* reader) { @@ -499,10 +499,11 @@ void Serializer::EncodeDocument(Writer* writer, // Encode Document.fields (unless it's empty) if (!object_value.internal_value.empty()) { - EncodeObjectMap(writer, object_value.internal_value, - google_firestore_v1beta1_Document_fields_tag, - google_firestore_v1beta1_Document_FieldsEntry_key_tag, - google_firestore_v1beta1_Document_FieldsEntry_value_tag); + EncodeObjectMapImpl( + writer, object_value.internal_value, + google_firestore_v1beta1_Document_fields_tag, + google_firestore_v1beta1_Document_FieldsEntry_key_tag, + google_firestore_v1beta1_Document_FieldsEntry_value_tag); } // Skip Document.create_time and Document.update_time, since they're @@ -667,6 +668,24 @@ std::unique_ptr Serializer::DecodeDocument(Reader* reader) const { /*has_local_modifications=*/false); } +void Serializer::EncodeObjectMap( + nanopb::Writer* writer, + const model::ObjectValue::Map& object_value_map, + uint32_t map_tag, + uint32_t key_tag, + uint32_t value_tag) { + // TODO(rsgowman): Move the implementation of EncodeObjectMapImpl here and + // eliminate that function. The only reason I'm (temporarily) keeping it, is + // that performing that refactoring now will cause a cascade of things that + // need to move into the local serializer class (as private functions). + EncodeObjectMapImpl(writer, object_value_map, map_tag, key_tag, value_tag); +} + +void Serializer::EncodeVersion(nanopb::Writer* writer, + const model::SnapshotVersion& version) { + EncodeTimestamp(writer, version.timestamp()); +} + } // namespace remote } // namespace firestore } // namespace firebase diff --git a/Firestore/core/src/firebase/firestore/remote/serializer.h b/Firestore/core/src/firebase/firestore/remote/serializer.h index 379069c..3b72693 100644 --- a/Firestore/core/src/firebase/firestore/remote/serializer.h +++ b/Firestore/core/src/firebase/firestore/remote/serializer.h @@ -38,6 +38,11 @@ namespace firebase { namespace firestore { + +namespace local { +class LocalSerializer; +} + namespace remote { /** @@ -158,13 +163,23 @@ class Serializer { return DecodeMaybeDocument(bytes.data(), bytes.size()); } + std::unique_ptr DecodeDocument(nanopb::Reader* reader) const; + + static void EncodeObjectMap(nanopb::Writer* writer, + const model::ObjectValue::Map& object_value_map, + uint32_t map_tag, + uint32_t key_tag, + uint32_t value_tag); + + static void EncodeVersion(nanopb::Writer* writer, + const model::SnapshotVersion& version); + private: void EncodeDocument(nanopb::Writer* writer, const model::DocumentKey& key, const model::ObjectValue& object_value) const; std::unique_ptr DecodeBatchGetDocumentsResponse( nanopb::Reader* reader) const; - std::unique_ptr DecodeDocument(nanopb::Reader* reader) const; const firebase::firestore::model::DatabaseId& database_id_; }; -- cgit v1.2.3