diff --git a/cpp/BUILD b/cpp/BUILD index 1776e802..d8f2251e 100644 --- a/cpp/BUILD +++ b/cpp/BUILD @@ -53,6 +53,22 @@ cc_test( testonly = True, ) +# Unit test for code area library +cc_test( + name = "codearea_test", + size = "small", + srcs = ["codearea_test.cc"], + copts = [ + "-pthread", + "-Iexternal/gtest/include", + ], + linkopts = ["-pthread"], + deps = [ + ":codearea", + "@gtest//:main", + ], +) + # Example binary for open location codes cc_binary( name = "openlocationcode_example", diff --git a/cpp/codearea.cc b/cpp/codearea.cc index 4d0c65ee..f40320b4 100644 --- a/cpp/codearea.cc +++ b/cpp/codearea.cc @@ -4,8 +4,11 @@ namespace openlocationcode { +namespace { const double kLatitudeMaxDegrees = 90; const double kLongitudeMaxDegrees = 180; +const CodeArea kInvalidCodeArea(0.0, 0.0, 0.0, 0.0, 0); +} // anonymous namespace CodeArea::CodeArea(double latitude_lo, double longitude_lo, double latitude_hi, double longitude_hi, size_t code_length) { @@ -36,4 +39,13 @@ LatLng CodeArea::GetCenter() const { return center; } +bool CodeArea::IsValid() const { + return code_length_ > 0 && latitude_lo_ < latitude_hi_ && + longitude_lo_ < longitude_hi_ && latitude_lo_ >= -90.0 && + latitude_hi_ <= 90.0 && longitude_lo_ >= -180.0 && + longitude_hi_ <= 180.0; +} + +const CodeArea& CodeArea::InvalidCodeArea() { return kInvalidCodeArea; } + } // namespace openlocationcode diff --git a/cpp/codearea.h b/cpp/codearea.h index 78abca18..d5e7ab5f 100644 --- a/cpp/codearea.h +++ b/cpp/codearea.h @@ -20,6 +20,10 @@ class CodeArea { double GetLongitudeHi() const; size_t GetCodeLength() const; LatLng GetCenter() const; + // Returns whether or not this area was a valid decode result. + bool IsValid() const; + // Returns an invalid CodeArea object. + static const CodeArea& InvalidCodeArea(); private: double latitude_lo_; diff --git a/cpp/codearea_test.cc b/cpp/codearea_test.cc new file mode 100644 index 00000000..cc0b8290 --- /dev/null +++ b/cpp/codearea_test.cc @@ -0,0 +1,59 @@ +#include "codearea.h" +#include "gtest/gtest.h" + +namespace openlocationcode { +namespace { + +TEST(CodeAreaChecks, Accessors) { + const CodeArea area(1.0, 2.0, 3.0, 4.0, 6); + // Check accessor methods return what we expect. + EXPECT_EQ(area.GetLatitudeLo(), 1.0); + EXPECT_EQ(area.GetLongitudeLo(), 2.0); + EXPECT_EQ(area.GetLatitudeHi(), 3.0); + EXPECT_EQ(area.GetLongitudeHi(), 4.0); + EXPECT_EQ(area.GetCodeLength(), 6); +} + +TEST(CodeAreaChecks, GetCenter) { + // Simple case. + const CodeArea area1(0.0, 0.0, 1.0, 2.0, 8); + EXPECT_EQ(area1.GetCenter().latitude, 0.5); + EXPECT_EQ(area1.GetCenter().longitude, 1.0); + // Negative latitudes & longitudes. + const CodeArea area2(-10.0, -30.0, -24.0, -84.0, 4); + EXPECT_EQ(area2.GetCenter().latitude, -17.0); + EXPECT_EQ(area2.GetCenter().longitude, -57.0); + // Latitude & longitude ranges crossing zero. + const CodeArea area3(-30.0, -17.0, 5.0, 21.0, 4); + EXPECT_EQ(area3.GetCenter().latitude, -12.5); + EXPECT_EQ(area3.GetCenter().longitude, 2.0); + // Zero-sized area (not strictly valid, but center is still well-defined). + const CodeArea area4(-65.0, 117.0, -65.0, 117.0, 2); + EXPECT_EQ(area4.GetCenter().latitude, -65.0); + EXPECT_EQ(area4.GetCenter().longitude, 117.0); +} + +TEST(CodeAreaChecks, IsValid) { + // All zeroes: invalid. + EXPECT_FALSE(CodeArea(0.0, 0.0, 0.0, 0.0, 0).IsValid()); + // Whole-world area: valid. + EXPECT_TRUE(CodeArea(-90.0, -180.0, 90.0, 180.0, 1).IsValid()); + // Typical area: valid. + EXPECT_TRUE(CodeArea(-1.0, -1.0, 1.0, 1.0, 10).IsValid()); + // Zero code length: invalid. + EXPECT_FALSE(CodeArea(-1.0, -1.0, 1.0, 1.0, 0).IsValid()); + // Low latitude >= high latitude: invalid. + EXPECT_FALSE(CodeArea(1.0, -1.0, 1.0, 1.0, 10).IsValid()); + EXPECT_FALSE(CodeArea(2.0, -1.0, 1.0, 1.0, 10).IsValid()); + // Low longitude >= high longitude: invalid. + EXPECT_FALSE(CodeArea(-1.0, 1.0, 1.0, 1.0, 10).IsValid()); + EXPECT_FALSE(CodeArea(-1.0, 2.0, 1.0, 1.0, 10).IsValid()); +} + +TEST(CodeAreaChecks, InvalidCodeArea) { + // CodeArea::InvalideCodeArea() must return an invalid code area, obviously. + EXPECT_FALSE(CodeArea::InvalidCodeArea().IsValid()); +} + +} // namespace +} // namespace openlocationcode diff --git a/cpp/openlocationcode.cc b/cpp/openlocationcode.cc index b763ff1c..929f0fcd 100644 --- a/cpp/openlocationcode.cc +++ b/cpp/openlocationcode.cc @@ -209,8 +209,13 @@ CodeArea Decode(const std::string &code) { // Define the place value for the most significant pair. int pv = pow(internal::kEncodingBase, internal::kPairCodeLength / 2 - 1); for (size_t i = 0; i < digits - 1; i += 2) { - normal_lat += get_alphabet_position(clean_code[i]) * pv; - normal_lng += get_alphabet_position(clean_code[i + 1]) * pv; + const int lat_dval = get_alphabet_position(clean_code[i]); + const int lng_dval = get_alphabet_position(clean_code[i + 1]); + if (lat_dval < 0 || lng_dval < 0) { + return CodeArea::InvalidCodeArea(); + } + normal_lat += lat_dval * pv; + normal_lng += lng_dval * pv; if (i < digits - 2) { pv /= internal::kEncodingBase; } @@ -227,6 +232,9 @@ CodeArea Decode(const std::string &code) { digits = std::min(internal::kMaximumDigitCount, clean_code.size()); for (size_t i = internal::kPairCodeLength; i < digits; i++) { int dval = get_alphabet_position(clean_code[i]); + if (dval < 0) { + return CodeArea::InvalidCodeArea(); + } int row = dval / internal::kGridColumns; int col = dval % internal::kGridColumns; extra_lat += row * row_pv; diff --git a/cpp/openlocationcode_test.cc b/cpp/openlocationcode_test.cc index 6e6b01e9..9780000c 100644 --- a/cpp/openlocationcode_test.cc +++ b/cpp/openlocationcode_test.cc @@ -170,6 +170,15 @@ TEST_P(EncodingChecks, Encode) { INSTANTIATE_TEST_CASE_P(OLC_Tests, EncodingChecks, ::testing::ValuesIn(GetEncodingDataFromCsv())); +TEST(DecodingChecks, DecodeOutOfRange) { + // Leading longitude digit "W" is out of range. + EXPECT_FALSE(Decode(std::string("9W4Q0000+")).IsValid()); + // Leading latitude digit "F" is out of range. + EXPECT_FALSE(Decode(std::string("F2+")).IsValid()); + // Invalid digits in post-separator portion. + EXPECT_FALSE(Decode(std::string("7Q7Q7Q7Q+5Z")).IsValid()); +} + struct ValidityTestData { std::string code; bool is_valid;