1 // Copyright Contributors to the OpenVDB Project
2 // SPDX-License-Identifier: MPL-2.0
3 
4 /*!
5     \file GridValidator.h
6 
7     \author Ken Museth
8 
9     \date August 30, 2020
10 
11     \brief Checks the validity of an existing NanoVDB grid.
12 */
13 
14 #ifndef NANOVDB_GRIDVALIDATOR_H_HAS_BEEN_INCLUDED
15 #define NANOVDB_GRIDVALIDATOR_H_HAS_BEEN_INCLUDED
16 
17 #include "../NanoVDB.h"
18 #include "GridChecksum.h"
19 
20 namespace nanovdb {
21 
22 /// @brief Return true if the specified grid passes several validation tests.
23 ///
24 /// @param grid Grid to validate
25 /// @param detailed If true the validation test is detailed and relatively slow.
26 /// @param verbose If true information about the first failed test is printed to std::cerr
27 template <typename ValueT>
28 bool isValid(const NanoGrid<ValueT> &grid, bool detailed = true, bool verbose = false);
29 
30 /// @brief Allows for the construction of NanoVDB grids without any dependecy
31 template <typename ValueT>
32 class GridValidator
33 {
34     using GridT = NanoGrid<ValueT>;
35     inline static void checkTree( const GridT&, std::string&, bool);
36     inline static void checkRoot( const GridT&, std::string&, bool);
37     inline static void checkNodes(const GridT&, std::string&);
38 
39 public:
40     /// @brief Returns an error message (an empty string means no error)
41     ///
42     /// @param grid NanoVDB grid to be tested
43     /// @param detailed If true the checksum is computed and validated as well as all the node pointers
44     ///
45     /// @note The validation is much slower if @c detailed == true!
46     static std::string check(const GridT &grid, bool detailed = true);
47 
48 };// GridValidator
49 
50 //================================================================================================
51 
52 template <typename ValueT>
check(const GridT & grid,bool detailed)53 std::string GridValidator<ValueT>::check(const GridT &grid, bool detailed)
54 {
55     std::string errorStr;
56 
57     // First check the Grid
58     auto *data = reinterpret_cast<const typename GridT::DataType*>(&grid);
59     std::stringstream ss;
60     if (data->mMagic != NANOVDB_MAGIC_NUMBER) {
61         ss << "Incorrect magic number: Expected " << NANOVDB_MAGIC_NUMBER << ", but read " << data->mMagic;
62         errorStr = ss.str();
63     } else if (!validateChecksum(grid, detailed ? ChecksumMode::Full : ChecksumMode::Partial)) {
64         errorStr.assign("Mis-matching checksum");
65     } else if (data->mVersion >= Version(29,0,0) && data->mVersion.getMajor() != NANOVDB_MAJOR_VERSION_NUMBER) {
66         ss << "Invalid major version number: Expected " << NANOVDB_MAJOR_VERSION_NUMBER << ", but read " << data->mVersion.c_str();
67         errorStr = ss.str();
68     } else if (data->mVersion < Version(29,0,0) && data->mVersion.id() != 28u) {
69         ss << "Invalid old major version number: Expected 28 or newer, but read " << data->mVersion.id();
70         errorStr = ss.str();
71     } else if (data->mGridClass >= GridClass::End) {
72         errorStr.assign("Invalid GridClass");
73      } else if (data->mGridType >= GridType::End) {
74         errorStr.assign("Invalid GridType");
75     } else if (data->mGridType != mapToGridType<ValueT>()) {
76         errorStr.assign("Invalid combination of ValueType and GridType");
77     } else if (!isValid(data->mGridType, data->mGridClass)) {
78         errorStr.assign("Invalid combination of GridType and GridClass");
79     } else if ( (const uint8_t*)(&(grid.tree())) != (const uint8_t*)(&grid+1) ) {
80         errorStr.assign("Invalid Tree pointer");
81     } else {
82         checkTree(grid, errorStr, detailed);
83     }
84     return errorStr;
85 }
86 
87 //================================================================================================
88 
89 template<typename ValueT>
checkTree(const GridT & grid,std::string & errorStr,bool detailed)90 void GridValidator<ValueT>::checkTree(const GridT &grid, std::string &errorStr, bool detailed)
91 {
92     if ( (const uint8_t*)(&grid.tree().root()) < (const uint8_t*)(&grid.tree()+1)) {
93        errorStr.assign("Invalid root pointer (should be located after the Grid and Tree)");
94     } else if ( (const uint8_t*)(&grid.tree().root()) > (const uint8_t*)(&grid) + grid.gridSize() - sizeof(grid.tree().root()) ) {
95        errorStr.assign("Invalid root pointer (appears to be located after the end of the buffer)");
96     } else {
97        checkRoot(grid, errorStr, detailed);
98     }
99 }// GridValidator::checkTree
100 
101 //================================================================================================
102 
103 template<typename ValueT>
checkRoot(const GridT & grid,std::string & errorStr,bool detailed)104 void GridValidator<ValueT>::checkRoot(const GridT &grid, std::string &errorStr, bool detailed)
105 {
106     auto &root = grid.tree().root();
107     auto *data = root.data();
108     const uint8_t *minPtr = (const uint8_t*)(&root + 1);
109     const uint8_t *maxPtr = (const uint8_t*)(&root) + root.memUsage();
110     for (uint32_t i = 0; errorStr.empty() && i<data->mTableSize; ++i) {
111         const auto *tile = data->tile(i);
112         if ( (const uint8_t *) tile < minPtr ) {
113             errorStr.assign("Invalid root tile pointer (below lower bound");
114         } else if ( (const uint8_t *) tile > maxPtr - sizeof(*tile) ) {
115             errorStr.assign("Invalid root tile pointer (above higher bound");
116         }
117     }
118     if (detailed && errorStr.empty()) {
119         checkNodes(grid, errorStr);
120     }
121 }// GridValidator::processRoot
122 
123 //================================================================================================
124 template<typename ValueT>
checkNodes(const GridT & grid,std::string & errorStr)125 void GridValidator<ValueT>::checkNodes(const GridT &grid, std::string &errorStr)
126 {
127     auto &root = grid.tree().root();// note, the root node was already checked
128     const uint8_t *minPtr = (const uint8_t*)(&root) + root.memUsage();
129     const uint8_t *maxPtr = (const uint8_t*)(&grid) + grid.gridSize();
130 
131     auto check = [&](const void * ptr, size_t ptrSize) -> bool {
132         if ( (const uint8_t *) ptr < minPtr ) {
133             errorStr.assign("Invalid node pointer: below lower bound");
134         } else if ( (const uint8_t *) ptr > maxPtr - ptrSize ) {
135             errorStr.assign("Invalid node pointer: above higher bound");
136         }
137         return errorStr.empty();
138     };
139 
140     auto *data3 = grid.tree().root().data();
141     for (uint32_t i=0, size=data3->mTableSize; i<size; ++i) {
142         auto *tile = data3->tile(i);// note, we already checked the root
143         if (!tile->isChild()) continue;
144         auto *node2 = data3->getChild(tile);
145         if (!check(node2, sizeof(*node2))) return;
146         auto *data2 = node2->data();
147         for (auto it2 = data2->mChildMask.beginOn(); it2; ++it2) {
148             auto *node1 = data2->getChild(*it2);
149             if (!check(node1, sizeof(*node1))) return;
150             auto *data1 = node1->data();
151             for (auto it1 = data1->mChildMask.beginOn(); it1; ++it1) {
152                 auto *leaf = data1->getChild(*it1);
153                 if (!check(leaf, sizeof(*leaf))) return;
154             }// loop over child nodes of the lower internal node
155         }// loop over child nodes of the upper internal node
156     }// loop over child nodes of the root node
157 } // GridValidator::processNodes
158 
159 
160 //================================================================================================
161 
162 template <typename ValueT>
isValid(const NanoGrid<ValueT> & grid,bool detailed,bool verbose)163 bool isValid(const NanoGrid<ValueT> &grid, bool detailed, bool verbose)
164 {
165     const std::string str = GridValidator<ValueT>::check( grid, detailed );
166     if (verbose && !str.empty()) std::cerr << "Validation failed: " << str << std::endl;
167     return str.empty();
168 }
169 
170 } // namespace nanovdb
171 
172 #endif // NANOVDB_GRIDVALIDATOR_H_HAS_BEEN_INCLUDED
173