1 /**
2 * Defines utility functions for testing CPER-JSON IR output from the cper-parse library.
3 *
4 * Author: Lawrence.Tang@arm.com
5 **/
6
7 #include <cstdio>
8 #include <cstdlib>
9 #include <fstream>
10 #include <filesystem>
11 #include "test-utils.hpp"
12
13 #include <libcper/BaseTypes.h>
14 #include <libcper/generator/cper-generate.h>
15
16 namespace fs = std::filesystem;
17
18 // Objects that have mutually exclusive fields (and thereforce can't have both
19 // required at the same time) can be added to this list.
20 // Truly optional properties that shouldn't be added to "required" field for
21 // validating the entire schema with validationbits=1
22 const static std::map<std::string, std::vector<std::string> >
23 optional_properties_map = {
24 { "./sections/cper-cxl-protocol.json",
25 { "capabilityStructure", "deviceSerial" } },
26 { "./sections/cper-cxl-component.json",
27 { "cxlComponentEventLog" } },
28 { "./sections/cper-ia32x64-processor.json",
29 { "addressSpace", "errorType", "participationType",
30 "timedOut", "level", "operation", "preciseIP",
31 "restartableIP", "overflow", "uncorrected",
32 "transactionType" } },
33 };
34
loadJson(const char * filePath)35 nlohmann::json loadJson(const char *filePath)
36 {
37 std::ifstream file(filePath);
38 if (!file.is_open()) {
39 std::cerr << "Failed to open file: " << filePath << std::endl;
40 }
41 nlohmann::json out = nlohmann::json::parse(file, nullptr, false);
42 return out;
43 }
44
45 //Returns a ready-for-use memory stream containing a CPER record with the given sections inside.
generate_record_memstream(const char ** types,UINT16 num_types,char ** buf,size_t * buf_size,int single_section,GEN_VALID_BITS_TEST_TYPE validBitsType)46 FILE *generate_record_memstream(const char **types, UINT16 num_types,
47 char **buf, size_t *buf_size,
48 int single_section,
49 GEN_VALID_BITS_TEST_TYPE validBitsType)
50 {
51 //Open a memory stream.
52 FILE *stream = open_memstream(buf, buf_size);
53
54 //Generate a section to the stream, close & return.
55 if (!single_section) {
56 generate_cper_record(const_cast<char **>(types), num_types,
57 stream, validBitsType);
58 } else {
59 generate_single_section_record(const_cast<char *>(types[0]),
60 stream, validBitsType);
61 }
62 fclose(stream);
63
64 //Return fmemopen() buffer for reading.
65 return fmemopen(*buf, *buf_size, "r");
66 }
67
iterate_make_required_props(nlohmann::json & jsonSchema,std::vector<std::string> & optional_props)68 void iterate_make_required_props(nlohmann::json &jsonSchema,
69 std::vector<std::string> &optional_props)
70 {
71 //id
72 const auto it_id = jsonSchema.find("$id");
73 if (it_id != jsonSchema.end()) {
74 auto id_strptr = it_id->get_ptr<const std::string *>();
75 std::string id_str = *id_strptr;
76 if (id_str.find("header") != std::string::npos ||
77 id_str.find("section-descriptor") != std::string::npos) {
78 return;
79 }
80 }
81 //oneOf
82 const auto it_oneof = jsonSchema.find("oneOf");
83 if (it_oneof != jsonSchema.end()) {
84 //Iterate over oneOf properties
85 for (auto &oneOfProp : *it_oneof) {
86 iterate_make_required_props(oneOfProp, optional_props);
87 }
88 }
89
90 //items
91 const auto it_items = jsonSchema.find("items");
92 if (it_items != jsonSchema.end()) {
93 iterate_make_required_props(*it_items, optional_props);
94 }
95 //required
96 const auto it_req = jsonSchema.find("required");
97 if (it_req == jsonSchema.end()) {
98 return;
99 }
100
101 //properties
102 const auto it_prop = jsonSchema.find("properties");
103 if (it_prop == jsonSchema.end()) {
104 return;
105 }
106 nlohmann::json &propertyFields = *it_prop;
107 nlohmann::json::array_t property_list;
108 if (propertyFields.is_object()) {
109 for (auto &[key, value] : propertyFields.items()) {
110 const auto it_find_opt_prop =
111 std::find(optional_props.begin(),
112 optional_props.end(), key);
113 if (it_find_opt_prop == optional_props.end()) {
114 //Add to list if property is not optional
115 property_list.push_back(key);
116 }
117
118 iterate_make_required_props(value, optional_props);
119 }
120 }
121
122 *it_req = property_list;
123 }
124
125 // Document loader callback function
documentLoader(const std::string & uri,AddRequiredProps add_required_props)126 const nlohmann::json *documentLoader(const std::string &uri,
127 AddRequiredProps add_required_props)
128 {
129 // Load the schema from a file
130 std::unique_ptr<nlohmann::json> ref_schema =
131 std::make_unique<nlohmann::json>();
132 *ref_schema = loadJson(uri.c_str());
133 if (ref_schema->is_discarded()) {
134 std::cerr << "Could not open schema file: " << uri << std::endl;
135 }
136 if (add_required_props == AddRequiredProps::YES) {
137 std::vector<std::string> opt = {};
138 const auto it_optional_file = optional_properties_map.find(uri);
139 if (it_optional_file != optional_properties_map.end()) {
140 opt = it_optional_file->second;
141 }
142 iterate_make_required_props(*ref_schema, opt);
143 }
144
145 return ref_schema.release();
146 }
147
148 // Document release callback function
documentRelease(const nlohmann::json * adapter)149 void documentRelease(const nlohmann::json *adapter)
150 {
151 delete adapter; // Free the adapter memory
152 }
153
154 std::unique_ptr<valijson::Schema>
load_schema(AddRequiredProps add_required_props,int single_section)155 load_schema(AddRequiredProps add_required_props, int single_section)
156 {
157 // Load the schema
158 fs::path pathObj(LIBCPER_JSON_SPEC);
159
160 if (single_section) {
161 pathObj /= "cper-json-section-log.json";
162 } else {
163 pathObj /= "cper-json-full-log.json";
164 }
165 nlohmann::json schema_root = loadJson(pathObj.c_str());
166 fs::path base_path(LIBCPER_JSON_SPEC);
167 try {
168 fs::current_path(base_path);
169 // std::cout << "Changed directory to: " << fs::current_path()
170 // << std::endl;
171 } catch (const fs::filesystem_error &e) {
172 std::cerr << "Filesystem error: " << e.what() << std::endl;
173 }
174
175 // Parse the json schema into an internal schema format
176 std::unique_ptr<valijson::Schema> schema =
177 std::make_unique<valijson::Schema>();
178 valijson::SchemaParser parser;
179 valijson::adapters::NlohmannJsonAdapter schemaDocumentAdapter(
180 schema_root);
181
182 // Set up callbacks for resolving external references
183 try {
184 parser.populateSchema(
185 schemaDocumentAdapter, *schema,
186 [add_required_props](const std::string &uri) {
187 return documentLoader(uri, add_required_props);
188 },
189 documentRelease);
190 } catch (std::exception &e) {
191 std::cerr << "Failed to parse schema: " << e.what()
192 << std::endl;
193 }
194 return schema;
195 }
196
schema_validate_from_file(const valijson::Schema & schema,nlohmann::json & jsonData,std::string & error_message)197 int schema_validate_from_file(const valijson::Schema &schema,
198 nlohmann::json &jsonData,
199 std::string &error_message)
200 {
201 // Perform validation
202 valijson::Validator validator(valijson::Validator::kStrongTypes);
203 valijson::ValidationResults results;
204 valijson::adapters::NlohmannJsonAdapter targetDocumentAdapter(jsonData);
205 if (!validator.validate(schema, targetDocumentAdapter, &results)) {
206 std::cerr << "Validation failed." << std::endl;
207 valijson::ValidationResults::Error error;
208 unsigned int errorNum = 1;
209 while (results.popError(error)) {
210 std::string context;
211 for (const std::string &str : error.context) {
212 context += str;
213 }
214
215 std::cout << "Error #" << errorNum << '\n'
216 << " context: " << context << '\n'
217 << " desc: " << error.description << '\n';
218 ++errorNum;
219 }
220 return 0;
221 }
222
223 error_message = "Schema validation successful";
224 return 1;
225 }
226