diff --git a/Firestore/Example/Firestore.xcodeproj/project.pbxproj b/Firestore/Example/Firestore.xcodeproj/project.pbxproj index 26cf2fffc4e..260b1d5867d 100644 --- a/Firestore/Example/Firestore.xcodeproj/project.pbxproj +++ b/Firestore/Example/Firestore.xcodeproj/project.pbxproj @@ -179,6 +179,7 @@ 7346E61D20325C6900FD6CEF /* FSTDispatchQueueTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 7346E61C20325C6900FD6CEF /* FSTDispatchQueueTests.mm */; }; 73866AA12082B0A5009BB4FF /* FIRArrayTransformTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 73866A9F2082B069009BB4FF /* FIRArrayTransformTests.mm */; }; 73FE5066020EF9B2892C86BF /* hard_assert_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 444B7AB3F5A2929070CB1363 /* hard_assert_test.cc */; }; + 84DBE646DCB49305879D3500 /* nanopb_string_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 353EEE078EF3F39A9B7279F6 /* nanopb_string_test.cc */; }; 873B8AEB1B1F5CCA007FD442 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 873B8AEA1B1F5CCA007FD442 /* Main.storyboard */; }; 8C82D4D3F9AB63E79CC52DC8 /* Pods_Firestore_IntegrationTests_iOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = ECEBABC7E7B693BE808A1052 /* Pods_Firestore_IntegrationTests_iOS.framework */; }; AB356EF7200EA5EB0089B766 /* field_value_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB356EF6200EA5EB0089B766 /* field_value_test.cc */; }; @@ -308,6 +309,7 @@ 2A0CF41BA5AED6049B0BEB2C /* type_traits_apple_test.mm */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.objcpp; path = type_traits_apple_test.mm; sourceTree = ""; }; 2B50B3A0DF77100EEE887891 /* Pods_Firestore_Tests_iOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Firestore_Tests_iOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 332485C4DCC6BA0DBB5E31B7 /* leveldb_util_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = leveldb_util_test.cc; sourceTree = ""; }; + 353EEE078EF3F39A9B7279F6 /* nanopb_string_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; name = nanopb_string_test.cc; path = nanopb/nanopb_string_test.cc; sourceTree = ""; }; 358C3B5FE573B1D60A4F7592 /* strerror_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = strerror_test.cc; sourceTree = ""; }; 3B843E4A1F3930A400548890 /* remote_store_spec_test.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = remote_store_spec_test.json; sourceTree = ""; }; 3C81DE3772628FE297055662 /* Pods-Firestore_Example_iOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_Example_iOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_Example_iOS/Pods-Firestore_Example_iOS.debug.xcconfig"; sourceTree = ""; }; @@ -729,6 +731,7 @@ 54EB764B202277970088B8F3 /* immutable */, 54995F70205B6E1A004EFFA0 /* local */, AB356EF5200E9D1A0089B766 /* model */, + 5C332D7293E6114E491D3662 /* nanopb */, 546854A720A3681B004BDBD5 /* remote */, 5467FB05203E652F009C9584 /* testutil */, 54740A561FC913EB00713A1A /* util */, @@ -781,6 +784,14 @@ path = immutable; sourceTree = ""; }; + 5C332D7293E6114E491D3662 /* nanopb */ = { + isa = PBXGroup; + children = ( + 353EEE078EF3F39A9B7279F6 /* nanopb_string_test.cc */, + ); + name = nanopb; + sourceTree = ""; + }; 5CAE131A20FFFED600BE9A4A /* Benchmarks */ = { isa = PBXGroup; children = ( @@ -1939,6 +1950,7 @@ AB6B908620322E6D00CC290A /* maybe_document_test.cc in Sources */, 618BBEA820B89AAC00B5BCE7 /* mutation.pb.cc in Sources */, 32F022CB75AEE48CDDAF2982 /* mutation_test.cc in Sources */, + 84DBE646DCB49305879D3500 /* nanopb_string_test.cc in Sources */, AB6B908820322E8800CC290A /* no_document_test.cc in Sources */, AB380D04201BC6E400D97691 /* ordered_code_test.cc in Sources */, 5A080105CCBFDB6BF3F3772D /* path_test.cc in Sources */, diff --git a/Firestore/core/CMakeLists.txt b/Firestore/core/CMakeLists.txt index c6ae0f5ac82..a7fd72a8508 100644 --- a/Firestore/core/CMakeLists.txt +++ b/Firestore/core/CMakeLists.txt @@ -31,5 +31,6 @@ add_subdirectory(test/firebase/firestore/core) add_subdirectory(test/firebase/firestore/immutable) add_subdirectory(test/firebase/firestore/local) add_subdirectory(test/firebase/firestore/model) +add_subdirectory(test/firebase/firestore/nanopb) add_subdirectory(test/firebase/firestore/remote) add_subdirectory(test/firebase/firestore/util) diff --git a/Firestore/core/src/firebase/firestore/nanopb/CMakeLists.txt b/Firestore/core/src/firebase/firestore/nanopb/CMakeLists.txt index 0471b1c8f34..c7bc8b8f59a 100644 --- a/Firestore/core/src/firebase/firestore/nanopb/CMakeLists.txt +++ b/Firestore/core/src/firebase/firestore/nanopb/CMakeLists.txt @@ -15,9 +15,11 @@ cc_library( firebase_firestore_nanopb SOURCES - tag.h + nanopb_string.cc + nanopb_string.h reader.h reader.cc + tag.h writer.h writer.cc DEPENDS diff --git a/Firestore/core/src/firebase/firestore/nanopb/nanopb_string.cc b/Firestore/core/src/firebase/firestore/nanopb/nanopb_string.cc new file mode 100644 index 00000000000..89a9be66db4 --- /dev/null +++ b/Firestore/core/src/firebase/firestore/nanopb/nanopb_string.cc @@ -0,0 +1,66 @@ +/* + * 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/nanopb/nanopb_string.h" + +#include +#include + +namespace firebase { +namespace firestore { +namespace nanopb { + +/* static */ pb_bytes_array_t* String::MakeBytesArray(absl::string_view value) { + auto size = static_cast(value.size()); + + // Allocate one extra byte for the null terminator that's not necessarily + // there in a string_view. As long as we're making a copy, might as well + // make a copy that won't overrun when used as a regular C string. This is + // essentially just to make debugging easier--actual user data can have + // embedded nulls so we shouldn't be using this as a C string under normal + // circumstances. + auto result = static_cast( + malloc(PB_BYTES_ARRAY_T_ALLOCSIZE(size) + 1)); + result->size = size; + memcpy(result->bytes, value.data(), size); + result->bytes[size] = '\0'; + + return result; +} + +pb_bytes_array_t* String::release() { + pb_bytes_array_t* result = bytes_; + bytes_ = nullptr; + return result; +} + +void swap(String& lhs, String& rhs) noexcept { + using std::swap; + swap(lhs.bytes_, rhs.bytes_); +} + +/* static */ String String::Wrap(pb_bytes_array_t* bytes) { + return String{bytes}; +} + +/* static */ absl::string_view String::ToStringView(pb_bytes_array_t* bytes) { + const char* str = reinterpret_cast(bytes->bytes); + return absl::string_view{str, bytes->size}; +} + +} // namespace nanopb +} // namespace firestore +} // namespace firebase diff --git a/Firestore/core/src/firebase/firestore/nanopb/nanopb_string.h b/Firestore/core/src/firebase/firestore/nanopb/nanopb_string.h new file mode 100644 index 00000000000..444d53d9541 --- /dev/null +++ b/Firestore/core/src/firebase/firestore/nanopb/nanopb_string.h @@ -0,0 +1,153 @@ +/* + * 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_NANOPB_NANOPB_STRING_H_ +#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_NANOPB_NANOPB_STRING_H_ + +#include + +#include +#include + +#include "Firestore/core/src/firebase/firestore/util/comparison.h" +#include "absl/strings/string_view.h" + +namespace firebase { +namespace firestore { +namespace nanopb { + +/** + * A string-like object backed by a nanopb byte array. + */ +class String : public util::Comparable { + public: + /** + * Creates a new, null-terminated byte array that's a copy of the given string + * value. + */ + static pb_bytes_array_t* MakeBytesArray(absl::string_view value); + + String() { + } + + /** + * Creates a new String whose backing byte array is a copy of the of the + * given C string. + */ + explicit String(const char* value) : bytes_{MakeBytesArray(value)} { + } + + /** + * Creates a new String whose backing byte array is a copy of the of the + * given string. + */ + explicit String(const std::string& value) : bytes_{MakeBytesArray(value)} { + } + + /** + * Creates a new String whose backing byte array is a copy of the of the + * given string_view. + */ + explicit String(absl::string_view value) : bytes_{MakeBytesArray(value)} { + } + + String(const String& other) + : bytes_{MakeBytesArray(absl::string_view{other})} { + } + + String(String&& other) noexcept : String{} { + swap(*this, other); + } + + ~String() { + delete bytes_; + } + + String& operator=(String other) { + swap(*this, other); + return *this; + } + + /** + * Creates a new String that takes ownership of the given byte array. + */ + static String Wrap(pb_bytes_array_t* bytes); + + bool empty() const { + return !bytes_ || bytes_->size == 0; + } + + /** + * Returns a pointer to the character data backing this String. The return + * value is `nullptr` if the backing bytes are themselves null. + */ + const char* data() const { + return bytes_ ? reinterpret_cast(bytes_->bytes) : nullptr; + } + + /** Returns a const view of the underlying byte array. */ + const pb_bytes_array_t* get() const { + return bytes_; + } + + /** + * Returns the current byte array and assigns the backing byte array to + * nullptr, releasing the ownership of the array contents to the caller. + */ + pb_bytes_array_t* release(); + + /** + * Converts this String to an absl::string_view (without changing ownership). + */ + explicit operator absl::string_view() const { + return ToStringView(bytes_); + } + + /** + * Swaps the contents of the given Strings. + */ + friend void swap(String& lhs, String& rhs) noexcept; + + friend bool operator==(const String& lhs, const String& rhs) { + return absl::string_view{lhs} == absl::string_view{rhs}; + } + friend bool operator<(const String& lhs, const String& rhs) { + return absl::string_view{lhs} < absl::string_view{rhs}; + } + + friend bool operator==(const String& lhs, absl::string_view rhs) { + absl::string_view lhs_view{lhs}; + return lhs_view == rhs; + } + + friend bool operator!=(const String& lhs, absl::string_view rhs) { + return !(lhs == rhs); + } + + private: + explicit String(pb_bytes_array_t* bytes) : bytes_{bytes} { + } + + static absl::string_view ToStringView(pb_bytes_array_t* bytes); + + pb_bytes_array_t* bytes_ = nullptr; +}; + +} // namespace nanopb +} // namespace firestore +} // namespace firebase + +#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_NANOPB_NANOPB_STRING_H_ diff --git a/Firestore/core/src/firebase/firestore/util/comparison.h b/Firestore/core/src/firebase/firestore/util/comparison.h index 64417a599bf..98262fc4140 100644 --- a/Firestore/core/src/firebase/firestore/util/comparison.h +++ b/Firestore/core/src/firebase/firestore/util/comparison.h @@ -179,6 +179,28 @@ bool DoubleBitwiseEquals(double left, double right); */ size_t DoubleBitwiseHash(double d); +template +class Equatable { + public: + friend bool operator!=(const T& lhs, const T& rhs) { + return !(lhs == rhs); + } +}; + +template +class Comparable : public Equatable { + public: + friend bool operator>(const T& lhs, const T& rhs) { + return rhs < lhs; + } + friend bool operator<=(const T& lhs, const T& rhs) { + return !(rhs < lhs); + } + friend bool operator>=(const T& lhs, const T& rhs) { + return !(lhs < rhs); + } +}; + } // namespace util } // namespace firestore } // namespace firebase diff --git a/Firestore/core/test/firebase/firestore/nanopb/CMakeLists.txt b/Firestore/core/test/firebase/firestore/nanopb/CMakeLists.txt new file mode 100644 index 00000000000..fda95d04c33 --- /dev/null +++ b/Firestore/core/test/firebase/firestore/nanopb/CMakeLists.txt @@ -0,0 +1,21 @@ +# 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. + +cc_test( + firebase_firestore_nanopb_test + SOURCES + nanopb_string_test.cc + DEPENDS + firebase_firestore_nanopb +) diff --git a/Firestore/core/test/firebase/firestore/nanopb/nanopb_string_test.cc b/Firestore/core/test/firebase/firestore/nanopb/nanopb_string_test.cc new file mode 100644 index 00000000000..5c89364c377 --- /dev/null +++ b/Firestore/core/test/firebase/firestore/nanopb/nanopb_string_test.cc @@ -0,0 +1,106 @@ +/* + * 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/nanopb/nanopb_string.h" + +#include "gtest/gtest.h" + +namespace firebase { +namespace firestore { +namespace nanopb { + +TEST(String, DefaultConstructor) { + String str; + EXPECT_EQ(nullptr, str.data()); +} + +TEST(String, FromStdString) { + std::string original{"foo"}; + String copy{original}; + EXPECT_EQ(copy, original); + + original = "bar"; + EXPECT_EQ(copy, "foo"); +} + +TEST(String, FromCString) { + char original[] = {'f', 'o', 'o', '\0'}; + String copy{original}; + EXPECT_EQ(copy, original); + + original[0] = 'b'; + EXPECT_EQ(copy, "foo"); +} + +TEST(String, WrapByteNullTerminatedArray) { + auto original = + static_cast(malloc(PB_BYTES_ARRAY_T_ALLOCSIZE(4))); + memcpy(original->bytes, "foo", 4); // null terminator + original->size = 3; + + String wrapper = String::Wrap(original); + EXPECT_EQ(wrapper, absl::string_view{"foo"}); + + original->bytes[0] = 'b'; + EXPECT_EQ(wrapper, absl::string_view{"boo"}); +} + +TEST(String, WrapByteUnterminatedArray) { + auto original = + static_cast(malloc(PB_BYTES_ARRAY_T_ALLOCSIZE(3))); + memcpy(original->bytes, "foo", 3); // no null terminator + original->size = 3; + + String wrapper = String::Wrap(original); + EXPECT_EQ(wrapper, absl::string_view{"foo"}); + + original->bytes[0] = 'b'; + EXPECT_EQ(wrapper, absl::string_view{"boo"}); +} + +TEST(String, Release) { + String value{"foo"}; + + pb_bytes_array_t* released = value.release(); + EXPECT_EQ(released->size, 3); + EXPECT_EQ(memcmp(released->bytes, "foo", 3), 0); + EXPECT_EQ(value.get(), nullptr); + + free(released); +} + +TEST(String, Comparison) { + String abc{"abc"}; + String def{"def"}; + + String abc2{"abc"}; + + EXPECT_TRUE(abc == abc); + EXPECT_TRUE(abc == abc2); + EXPECT_TRUE(abc != def); + + EXPECT_TRUE(abc < def); + EXPECT_TRUE(abc <= def); + EXPECT_TRUE(abc <= abc2); + + EXPECT_TRUE(def > abc); + EXPECT_TRUE(def >= abc); + EXPECT_TRUE(abc2 >= abc); +} + +} // namespace nanopb +} // namespace firestore +} // namespace firebase