// Copyright (c) 2017, 2024, Oracle and/or its affiliates. // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License, version 2.0, // as published by the Free Software Foundation. // // This program is designed to work with certain software (including // but not limited to OpenSSL) that is licensed under separate terms, // as designated in a particular file or component or in included license // documentation. The authors of MySQL hereby grant you an additional // permission to link the program and your derivative works with the // separately licensed software that they have either included with // the program or referenced in the documentation. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License, version 2.0, for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. #include "sql/gis/wkb.h" #include // M_PI, M_PI_2 #include #include "my_byteorder.h" // float8get, int4store, uint4korr #include "my_sys.h" // my_error() #include "myisampack.h" #include "mysqld_error.h" #include "sql/check_stack.h" // check_stack_overrun #include "sql/gis/coordinate_range_visitor.h" #include "sql/gis/geometries.h" #include "sql/gis/geometries_cs.h" #include "sql/gis/ring_flip_visitor.h" #include "sql/gis/wkb_size_visitor.h" #include "sql/gis/wkb_visitor.h" #include "sql/sql_const.h" // STACK_MIN_SIZE #include "sql/sql_error.h" #include "sql/srs_fetcher.h" #include "sql_string.h" #include "template_utils.h" // pointer_cast namespace gis { /// WKB endianness. enum class Byte_order : std::uint8_t { /// Big endian XDR = 0, /// Little endian NDR = 1 }; /// Checks if a given type is a valid (and supported) WKB type. /// /// @param type The type to check /// /// @retval true The type is valid. /// @retval false The type is invalid. static bool is_valid_type(Geometry_type type) { switch (type) { case Geometry_type::kPoint: case Geometry_type::kLinestring: case Geometry_type::kPolygon: case Geometry_type::kMultipoint: case Geometry_type::kMultilinestring: case Geometry_type::kMultipolygon: case Geometry_type::kGeometrycollection: return true; default: return false; /* purecov: inspected */ } } /// Checks if a given type is a subtype of a given supertype. /// /// @param sub The type to check. /// @param super The supertype. /// /// @retval true The type is the supertype or a subtype of it. /// @retval false The type is neither the supertype nor a subtype of it. static bool is_subtype_of(Geometry_type sub, Geometry_type super) { return (super == Geometry_type::kGeometry || sub == super || (super == Geometry_type::kGeometrycollection && (sub == Geometry_type::kMultipoint || sub == Geometry_type::kMultilinestring || sub == Geometry_type::kMultipolygon))); } /// Checks if a given type is a valid type and that it is a subtype of a given /// supertype. /// /// @param sub The type to check. /// @param super The supertype. /// /// @retval true The type is a valid subtype of the supertype. /// @retval false The type is invalid or not a subtype of the supertype. static bool is_valid_type_or_subtype(Geometry_type sub, Geometry_type super) { return is_valid_type(sub) && is_subtype_of(sub, super); } template class Wkb_parser { private: uchar *m_begin; uchar *m_end; Coordinate_system m_coordinate_system; double m_angular_unit; double m_prime_meridian; bool m_positive_north; bool m_positive_east; bool m_swap_axes; THD *m_thd; double transform_x(double x) { assert(!std::isnan(x)); switch (m_coordinate_system) { case Coordinate_system::kCartesian: // The on-disk and in-memory format is x in SRS direction and unit. break; case Coordinate_system::kGeographic: // The on-disk format is x = longitude, in the SRS direction and unit, // and with the SRS meridian. // The in-memory format is x = longitude (Easting) in radians with the // meridian at Greenwich. if (!m_positive_east) x *= -1.0; x += m_prime_meridian; // Both are in the SRS angular unit x *= m_angular_unit; // Convert to radians break; default: assert(false); /* purecov: inspected */ break; } assert(!std::isnan(x)); return x; } double transform_y(double y) { assert(!std::isnan(y)); switch (m_coordinate_system) { case Coordinate_system::kCartesian: // The on-disk and in-memory format is y in SRS direction and unit. break; case Coordinate_system::kGeographic: // The on-disk format is y = latitude, in the SRS direction and unit. // The in-memory format is y = latitude (Northing) in radians. if (!m_positive_north) y *= -1.0; y *= m_angular_unit; // Convert to radians break; default: assert(false); /* purecov: inspected */ break; } assert(!std::isnan(y)); return y; } public: Wkb_parser(THD *thd, const dd::Spatial_reference_system *srs, bool ignore_axis_order, uchar *begin, uchar *end) : m_begin(begin), m_end(end), m_coordinate_system(Coordinate_system::kCartesian), m_angular_unit(1.0), m_prime_meridian(0.0), m_positive_north(true), m_positive_east(true), m_swap_axes(false), m_thd(thd) { if (srs == nullptr || srs->is_cartesian()) { m_coordinate_system = Coordinate_system::kCartesian; } else if (srs->is_geographic()) { m_coordinate_system = Coordinate_system::kGeographic; m_angular_unit = srs->angular_unit(); m_prime_meridian = srs->prime_meridian(); m_positive_north = srs->positive_north(); m_positive_east = srs->positive_east(); if (!ignore_axis_order && srs->is_lat_long()) m_swap_axes = true; } } Byte_order parse_byte_order() { if (m_begin + 1 > m_end) throw std::exception(); switch (*(m_begin++)) { case 0: return Byte_order::XDR; case 1: return Byte_order::NDR; } throw std::exception(); /* purecov: inspected */ } bool reached_end() const { return m_begin == m_end; } std::uint32_t parse_uint32(Byte_order bo) { if (m_begin + sizeof(std::uint32_t) > m_end) throw std::exception(); std::uint32_t i; if (bo == Byte_order::NDR) { i = uint4korr(m_begin); } else { i = load32be(m_begin); } m_begin += 4; return i; } double parse_double(Byte_order bo) { if (m_begin + sizeof(double) > m_end) throw std::exception(); double d; if (bo == Byte_order::NDR) { // Little endian data. Use conversion functions to native endianness. d = float8get(m_begin); } else { d = mi_float8get(m_begin); } m_begin += sizeof(double); return d; } Geometry_type parse_geometry_type(Byte_order bo) { if (m_begin + sizeof(std::uint32_t) > m_end) { throw std::exception(); } std::uint32_t wkb_type = parse_uint32(bo); Geometry_type type = static_cast(wkb_type); if (!is_valid_type_or_subtype(type, Geometry_type::kGeometry)) throw std::exception(); return type; } Point_t parse_point(Byte_order bo) { double x = parse_double(bo); double y = parse_double(bo); if (!std::isfinite(x) || !std::isfinite(y)) throw std::exception(); if (m_swap_axes) return Point_t(transform_x(y), transform_y(x)); else return Point_t(transform_x(x), transform_y(y)); } Point_t parse_wkb_point() { if (m_thd != nullptr && check_stack_overrun(m_thd, STACK_MIN_SIZE, nullptr)) throw std::exception(); Byte_order bo = parse_byte_order(); Geometry_type type = parse_geometry_type(bo); if (type != Geometry_type::kPoint) throw std::exception(); return parse_point(bo); } Linestring_t parse_linestring(Byte_order bo) { Linestring_t ls; std::uint32_t num_points = parse_uint32(bo); if (num_points < 2) throw std::exception(); for (std::uint32_t i = 0; i < num_points; i++) { ls.push_back(parse_point(bo)); } return ls; } Linestring_t parse_wkb_linestring() { if (m_thd != nullptr && check_stack_overrun(m_thd, STACK_MIN_SIZE, nullptr)) throw std::exception(); Byte_order bo = parse_byte_order(); Geometry_type type = parse_geometry_type(bo); if (type != Geometry_type::kLinestring) throw std::exception(); return parse_linestring(bo); } Polygon_t parse_polygon(Byte_order bo) { Polygon_t py; std::uint32_t num_rings = parse_uint32(bo); if (num_rings == 0) throw std::exception(); for (std::uint32_t i = 0; i < num_rings; i++) { Linearring_t lr; std::uint32_t num_points = parse_uint32(bo); if (num_points < 4) throw std::exception(); for (std::uint32_t j = 0; j < num_points; j++) { lr.push_back(parse_point(bo)); } py.push_back(std::forward(lr)); } return py; } Polygon_t parse_wkb_polygon() { if (m_thd != nullptr && check_stack_overrun(m_thd, STACK_MIN_SIZE, nullptr)) throw std::exception(); Byte_order bo = parse_byte_order(); Geometry_type type = parse_geometry_type(bo); if (type != Geometry_type::kPolygon) throw std::exception(); return parse_polygon(bo); } Multipoint_t parse_multipoint(Byte_order bo) { if (m_thd != nullptr && check_stack_overrun(m_thd, STACK_MIN_SIZE, nullptr)) throw std::exception(); Multipoint_t mpt; std::uint32_t num_points = parse_uint32(bo); if (num_points == 0) throw std::exception(); for (std::uint32_t i = 0; i < num_points; i++) { mpt.push_back(parse_wkb_point()); } return mpt; } Multilinestring_t parse_multilinestring(Byte_order bo) { if (m_thd != nullptr && check_stack_overrun(m_thd, STACK_MIN_SIZE, nullptr)) throw std::exception(); Multilinestring_t mls; std::uint32_t num_linestrings = parse_uint32(bo); if (num_linestrings == 0) throw std::exception(); for (std::uint32_t i = 0; i < num_linestrings; i++) { Linestring_t ls; mls.push_back(parse_wkb_linestring()); } return mls; } Multipolygon_t parse_multipolygon(Byte_order bo) { if (m_thd != nullptr && check_stack_overrun(m_thd, STACK_MIN_SIZE, nullptr)) throw std::exception(); Multipolygon_t mpy; std::uint32_t num_polygons = parse_uint32(bo); if (num_polygons == 0) throw std::exception(); for (std::uint32_t i = 0; i < num_polygons; i++) { mpy.push_back(parse_wkb_polygon()); } return mpy; } Geometrycollection_t parse_geometrycollection(Byte_order bo) { if (m_thd != nullptr && check_stack_overrun(m_thd, STACK_MIN_SIZE, nullptr)) throw std::exception(); Geometrycollection_t gc; std::uint32_t num_geometries = parse_uint32(bo); for (std::uint32_t i = 0; i < num_geometries; i++) { Geometry *g = parse_wkb(); gc.push_back(std::move(*g)); delete g; } return gc; } Geometry *parse_wkb() { Byte_order bo = parse_byte_order(); Geometry_type type = parse_geometry_type(bo); switch (type) { case Geometry_type::kPoint: return new Point_t(parse_point(bo)); case Geometry_type::kLinestring: return new Linestring_t(parse_linestring(bo)); case Geometry_type::kPolygon: return new Polygon_t(parse_polygon(bo)); case Geometry_type::kMultipoint: return new Multipoint_t(parse_multipoint(bo)); case Geometry_type::kMultilinestring: return new Multilinestring_t(parse_multilinestring(bo)); case Geometry_type::kMultipolygon: return new Multipolygon_t(parse_multipolygon(bo)); case Geometry_type::kGeometrycollection: return new Geometrycollection_t(parse_geometrycollection(bo)); default: throw std::exception(); /* purecov: inspected */ } } }; std::unique_ptr parse_wkb(THD *thd, const dd::Spatial_reference_system *srs, const char *wkb, std::size_t length, bool ignore_axis_order) { unsigned char *begin = pointer_cast(const_cast(wkb)); unsigned char *end = begin + length; std::unique_ptr g = nullptr; bool res; if (srs == nullptr || srs->is_cartesian()) { try { Wkb_parser parser(thd, srs, ignore_axis_order, begin, end); g.reset(parser.parse_wkb()); res = !g || !parser.reached_end(); } catch (...) { res = true; } } else if (srs->is_geographic()) { try { Wkb_parser parser(thd, srs, ignore_axis_order, begin, end); g.reset(parser.parse_wkb()); res = !g || !parser.reached_end(); } catch (...) { res = true; } } else { assert(false); /* purecov: inspected */ return std::unique_ptr(); } if (res) { return std::unique_ptr(); } return g; } bool parse_srid(const char *str, std::size_t length, srid_t *srid) { unsigned char *begin = pointer_cast(const_cast(str)); if (length < sizeof(srid_t)) return true; *srid = uint4korr(begin); // Always little-endian. return false; } bool parse_geometry(THD *thd, const char *func_name, const String *str, const dd::Spatial_reference_system **srs, std::unique_ptr *geometry, bool treat_unknown_srid_as_cartesian) { srid_t srid; if (parse_srid(str->ptr(), str->length(), &srid)) { my_error(ER_GIS_INVALID_DATA, MYF(0), func_name); return true; } Srs_fetcher fetcher(thd); *srs = nullptr; if (srid != 0 && fetcher.acquire(srid, srs)) return true; if (srid != 0 && *srs == nullptr && !treat_unknown_srid_as_cartesian) { my_error(ER_SRS_NOT_FOUND, MYF(0), srid); return true; } *geometry = gis::parse_wkb(thd, *srs, str->ptr() + sizeof(srid_t), str->length() - sizeof(srid_t), true); if (!(*geometry)) { // Parsing failed, assume invalid input data. my_error(ER_GIS_INVALID_DATA, MYF(0), func_name); return true; } // Flip polygon rings so that the exterior ring is counter-clockwise and // interior rings are clockwise. double semi_major = 1.0; double semi_minor = 1.0; if (*srs && (*srs)->is_geographic()) { semi_major = (*srs)->semi_major_axis(); semi_minor = (*srs)->semi_minor_axis(); } gis::Ring_flip_visitor rfv(semi_major, semi_minor); (*geometry)->accept(&rfv); if (rfv.invalid()) { // There's something wrong with a polygon in the geometry. my_error(ER_GIS_INVALID_DATA, MYF(0), func_name); return true; } gis::Coordinate_range_visitor crv(*srs); if ((*geometry)->accept(&crv)) { if (crv.longitude_out_of_range()) { my_error(ER_GEOMETRY_PARAM_LONGITUDE_OUT_OF_RANGE, MYF(0), func_name, crv.coordinate_value(), (*srs)->from_radians(-M_PI), (*srs)->from_radians(M_PI)); return true; } if (crv.latitude_out_of_range()) { my_error(ER_GEOMETRY_PARAM_LATITUDE_OUT_OF_RANGE, MYF(0), func_name, crv.coordinate_value(), (*srs)->from_radians(-M_PI_2), (*srs)->from_radians(M_PI_2)); return true; } } return false; } bool write_geometry(const dd::Spatial_reference_system *srs, Geometry &geometry, String *str) { Wkb_size_visitor wkb_size; geometry.accept(&wkb_size); size_t geometry_size = sizeof(std::uint32_t) + wkb_size.size(); // SRID + WKB. str->set_charset(&my_charset_bin); if (str->reserve(geometry_size)) { /* purecov: begin inspected */ my_error(ER_OUTOFMEMORY, MYF(0)); return true; /* purecov: end */ } str->length(geometry_size); char *buffer = &((*str)[0]); int4store(buffer, srs == nullptr ? 0 : srs->id()); buffer += sizeof(std::uint32_t); Wkb_visitor wkb(srs, buffer, wkb_size.size()); geometry.accept(&wkb); return false; } } // namespace gis