Use less c++

As a design, it would be better if we didn't need to have a dependency
on c++ in a c library.  Moving things to C will reduce both
dependencies and compile times.

Reduced dependencies makes the library itself easier to use in
more environments.

libmctp Is a library that doesn't take a dependency on gtest for its
tests
libpldm Is an example that does take a dependency on gtest.

Change-Id: If1dc638f87b75b28181a0baf67f5a18d389cff81
Signed-off-by: Ed Tanous <etanous@nvidia.com>
diff --git a/cli-app/cper-convert.c b/cli-app/cper-convert.c
index 082e452..942d258 100644
--- a/cli-app/cper-convert.c
+++ b/cli-app/cper-convert.c
@@ -15,7 +15,7 @@
 #include <libcper/json-schema.h>
 
 void cper_to_json(char *in_file, char *out_file, int is_single_section);
-void json_to_cper(char *in_file, char *out_file);
+void json_to_cper(const char *in_file, const char *out_file);
 void print_help(void);
 
 int main(int argc, char *argv[])
@@ -128,7 +128,7 @@
 }
 
 //Command for converting a provided CPER-JSON JSON file to CPER binary.
-void json_to_cper(char *in_file, char *out_file)
+void json_to_cper(const char *in_file, const char *out_file)
 {
 	//Verify output file exists.
 	if (out_file == NULL) {
diff --git a/include/libcper/log.h b/include/libcper/log.h
index ccfca59..445d4d2 100644
--- a/include/libcper/log.h
+++ b/include/libcper/log.h
@@ -3,9 +3,17 @@
 #ifndef LIBCPER_LOG_H
 #define LIBCPER_LOG_H
 
+#ifdef __cplusplus
+extern "C" {
+#endif
+
 void cper_set_log_stdio();
 void cper_set_log_custom(void (*fn)(const char *, ...));
 
 void cper_print_log(const char *fmt, ...) __attribute__((format(printf, 1, 2)));
 
+#ifdef __cplusplus
+}
+#endif
+
 #endif /* LIBCPER_LOG_H */
diff --git a/meson.build b/meson.build
index e669bb8..96e194c 100644
--- a/meson.build
+++ b/meson.build
@@ -21,6 +21,8 @@
 add_project_arguments('-DLIBCPER_EXAMPLES="'
     + meson.current_source_dir() + '/examples"', language: ['c', 'cpp'])
 
+add_project_arguments('-Wno-unused-function', language: ['c', 'cpp'])
+
 library_is_share = get_option('default_library') == 'shared'
 add_project_arguments('-D_POSIX_C_SOURCE=200809L', language: 'c')
 
@@ -74,11 +76,9 @@
 cc = meson.get_compiler('c')
 
 json_c_dep = dependency('json-c')
-
 libcper_include = ['include']
 libcper_include_dir = include_directories(libcper_include, is_system: true)
 
-
 libcper_parse_sources += files(
     'base64.c',
     'common-utils.c',
diff --git a/tests/base64_test.cpp b/tests/base64_test.cpp
index 6040c00..058de81 100644
--- a/tests/base64_test.cpp
+++ b/tests/base64_test.cpp
@@ -9,15 +9,15 @@
 	std::array<uint8_t, 1> data = { 'f' };
 	char *encoded = base64_encode(data.data(), data.size(), &encoded_len);
 	EXPECT_EQ(encoded_len, 4);
-	ASSERT_EQ(std::string_view(encoded, encoded_len), "Zg==");
+	ASSERT_TRUE(memcmp(encoded, "Zg==", encoded_len) == 0);
 	free(encoded);
 }
 
 TEST(Base64Decode, Good)
 {
 	int32_t decoded_len = 0;
-	std::string_view data{ "Zg==" };
-	UINT8 *decoded = base64_decode(data.data(), data.size(), &decoded_len);
+	const char *data{ "Zg==" };
+	UINT8 *decoded = base64_decode(data, strlen(data), &decoded_len);
 	EXPECT_EQ(decoded_len, 1);
 	ASSERT_EQ(decoded[0], 'f');
 	free(decoded);
diff --git a/tests/fuzz_cper_buf_to_ir.cpp b/tests/fuzz_cper_buf_to_ir.c
similarity index 74%
rename from tests/fuzz_cper_buf_to_ir.cpp
rename to tests/fuzz_cper_buf_to_ir.c
index b947782..a9ff504 100644
--- a/tests/fuzz_cper_buf_to_ir.cpp
+++ b/tests/fuzz_cper_buf_to_ir.c
@@ -1,8 +1,8 @@
-#include <cassert>
+#include <assert.h>
 #include "libcper/cper-parse.h"
-#include "test-utils.hpp"
+#include "test-utils.h"
 
-extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
 {
 	json_object *ir = cper_buf_to_ir(data, size);
 	if (ir == NULL) {
diff --git a/tests/ir-tests.cpp b/tests/ir-tests.cpp
index b30e2ec..37ee830 100644
--- a/tests/ir-tests.cpp
+++ b/tests/ir-tests.cpp
@@ -5,12 +5,7 @@
  **/
 
 #include <gtest/gtest.h>
-#include "test-utils.hpp"
-#include <cctype>
-#include <charconv>
-#include <filesystem>
-#include <format>
-#include <fstream>
+#include "test-utils.h"
 #include <json.h>
 #include <libcper/cper-parse.h>
 #include <libcper/generator/cper-generate.h>
@@ -27,80 +22,172 @@
 static const GEN_VALID_BITS_TEST_TYPE fixedValidbitsSet = SOME_VALID;
 static const int GEN_EXAMPLES = 0;
 
+static const char *cper_ext = "cperhex";
+static const char *json_ext = "json";
+
+struct file_info {
+	char *cper_out;
+	char *json_out;
+};
+
+void free_file_info(file_info *info)
+{
+	if (info == NULL) {
+		return;
+	}
+	free(info->cper_out);
+	free(info->json_out);
+	free(info);
+}
+
+file_info *file_info_init(const char *section_name)
+{
+	file_info *info = NULL;
+	char *buf = NULL;
+	size_t size;
+	int ret;
+
+	info = (file_info *)calloc(1, sizeof(file_info));
+	if (info == NULL) {
+		goto fail;
+	}
+
+	size = strlen(LIBCPER_EXAMPLES) + 1 + strlen(section_name) + 1 +
+	       strlen(cper_ext) + 1;
+	info->cper_out = (char *)malloc(size);
+	ret = snprintf(info->cper_out, size, "%s/%s.%s", LIBCPER_EXAMPLES,
+		       section_name, cper_ext);
+	if (ret != (int)size - 1) {
+		printf("snprintf0 failed\n");
+		goto fail;
+	}
+	size = strlen(LIBCPER_EXAMPLES) + 1 + strlen(section_name) + 1 +
+	       strlen(json_ext) + 1;
+	info->json_out = (char *)malloc(size);
+	ret = snprintf(info->json_out, size, "%s/%s.%s", LIBCPER_EXAMPLES,
+		       section_name, json_ext);
+	if (ret != (int)size - 1) {
+		printf("snprintf3 failed\n");
+		goto fail;
+	}
+	free(buf);
+	return info;
+
+fail:
+	free(buf);
+	free_file_info(info);
+	return NULL;
+}
+
 void cper_create_examples(const char *section_name)
 {
 	//Generate full CPER record for the given type.
-	fs::path file_path = LIBCPER_EXAMPLES;
-	file_path /= section_name;
-	fs::path cper_out = file_path.replace_extension("cperhex");
-	fs::path json_out = file_path.replace_extension("json");
-
-	char *buf;
+	json_object *ir = NULL;
 	size_t size;
-	FILE *record = generate_record_memstream(&section_name, 1, &buf, &size,
-						 0, fixedValidbitsSet);
-
-	// Write example CPER to disk
-	std::ofstream outFile(cper_out, std::ios::binary);
-	if (!outFile.is_open()) {
-		std::cerr << "Failed to create/open CPER output file: "
-			  << cper_out << std::endl;
-		return;
+	size_t file_size;
+	FILE *outFile = NULL;
+	std::vector<unsigned char> file_data;
+	FILE *record = NULL;
+	char *buf = NULL;
+	file_info *info = file_info_init(section_name);
+	if (info == NULL) {
+		goto done;
 	}
 
-	std::vector<unsigned char> file_data;
+	record = generate_record_memstream(&section_name, 1, &buf, &size, 0,
+					   fixedValidbitsSet);
+
+	// Write example CPER to disk
+	outFile = fopen(info->cper_out, "wb");
+	if (outFile == NULL) {
+		std::cerr << "Failed to create/open CPER output file: "
+			  << info->cper_out << std::endl;
+		goto done;
+	}
+
 	fseek(record, 0, SEEK_END);
-	size_t file_size = ftell(record);
+	file_size = ftell(record);
 	rewind(record);
 	file_data.resize(file_size);
 	if (fread(file_data.data(), 1, file_data.size(), record) != file_size) {
 		std::cerr << "Failed to read CPER data from memstream."
 			  << std::endl;
-		FAIL();
-		return;
+		EXPECT_EQ(false, true);
+		fclose(outFile);
+		goto done;
 	}
 	for (size_t index = 0; index < file_data.size(); index++) {
-		outFile << std::format("{:02x}", file_data[index]);
+		char hex_str[3];
+		int out = snprintf(hex_str, sizeof(hex_str), "%02x",
+				   file_data[index]);
+		if (out != 2) {
+			printf("snprintf1 failed\n");
+			goto done;
+		}
+		fwrite(hex_str, sizeof(char), 2, outFile);
 		if (index % 30 == 29) {
-			outFile << "\n";
+			fwrite("\n", sizeof(char), 1, outFile);
 		}
 	}
-	outFile.close();
+	fclose(outFile);
 
 	//Convert to IR, free resources.
 	rewind(record);
-	json_object *ir = cper_to_ir(record);
+	ir = cper_to_ir(record);
 	if (ir == NULL) {
 		std::cerr << "Empty JSON from CPER bin" << std::endl;
-		FAIL();
-		return;
+		EXPECT_EQ(false, true);
+		goto done;
 	}
 
 	//Write json output to disk
-	json_object_to_file_ext(json_out.c_str(), ir, JSON_C_TO_STRING_PRETTY);
+	json_object_to_file_ext(info->json_out, ir, JSON_C_TO_STRING_PRETTY);
 	json_object_put(ir);
 
-	fclose(record);
+done:
+	free_file_info(info);
+	if (record != NULL) {
+		fclose(record);
+	}
+	if (outFile != NULL) {
+		fclose(outFile);
+	}
 	free(buf);
 }
 
-std::vector<unsigned char> string_to_binary(const std::string &source)
+int hex2int(char ch)
+{
+	if ((ch >= '0') && (ch <= '9')) {
+		return ch - '0';
+	}
+	if ((ch >= 'A') && (ch <= 'F')) {
+		return ch - 'A' + 10;
+	}
+	if ((ch >= 'a') && (ch <= 'f')) {
+		return ch - 'a' + 10;
+	}
+	return -1;
+}
+
+std::vector<unsigned char> string_to_binary(const char *source, size_t length)
 {
 	std::vector<unsigned char> retval;
 	bool uppernibble = true;
-	for (const char c : source) {
-		unsigned char val = 0;
+	for (size_t i = 0; i < length; i++) {
+		char c = source[i];
 		if (c == '\n') {
 			continue;
 		}
-		std::from_chars_result r = std::from_chars(&c, &c + 1, val, 16);
-		EXPECT_TRUE(r.ec == std::error_code())
-			<< "Invalid hex character in test file: " << c;
+		int val = hex2int(c);
+		if (val < 0) {
+			printf("Invalid hex character in test file: %c\n", c);
+			return {};
+		}
 
 		if (uppernibble) {
-			retval.push_back(val << 4);
+			retval.push_back((unsigned char)(val << 4));
 		} else {
-			retval.back() += val;
+			retval.back() += (unsigned char)val;
 		}
 		uppernibble = !uppernibble;
 	}
@@ -111,32 +198,52 @@
 void cper_example_section_ir_test(const char *section_name)
 {
 	//Open CPER record for the given type.
-	fs::path fpath = LIBCPER_EXAMPLES;
-	fpath /= section_name;
-	fs::path cper = fpath.replace_extension("cperhex");
-	fs::path json = fpath.replace_extension("json");
+	file_info *info = file_info_init(section_name);
+	if (info == NULL) {
+		return;
+	}
 
-	std::ifstream cper_file(cper, std::ios::binary);
-	if (!cper_file.is_open()) {
-		std::cerr << "Failed to open CPER file: " << cper << std::endl;
+	FILE *cper_file = fopen(info->cper_out, "rb");
+	if (cper_file == NULL) {
+		std::cerr << "Failed to open CPER file: " << info->cper_out
+			  << std::endl;
+		free_file_info(info);
 		FAIL() << "Failed to open CPER file";
 		return;
 	}
-	std::string cper_str((std::istreambuf_iterator<char>(cper_file)),
-			     std::istreambuf_iterator<char>());
+	fseek(cper_file, 0, SEEK_END);
+	size_t length = ftell(cper_file);
+	fseek(cper_file, 0, SEEK_SET);
+	char *buffer = (char *)malloc(length);
+	if (!buffer) {
+		free_file_info(info);
+		return;
+	}
+	if (fread(buffer, 1, length, cper_file) != length) {
+		std::cerr << "Failed to read CPER file: " << info->cper_out
+			  << std::endl;
+		free(buffer);
+		free_file_info(info);
+		return;
+	}
+	fclose(cper_file);
 
-	std::vector<unsigned char> cper_bin = string_to_binary(cper_str);
+	std::vector<unsigned char> cper_bin = string_to_binary(buffer, length);
 	//Convert to IR, free resources.
 	json_object *ir = cper_buf_to_ir(cper_bin.data(), cper_bin.size());
 	if (ir == NULL) {
 		std::cerr << "Empty JSON from CPER bin" << std::endl;
+		free(buffer);
+		free_file_info(info);
 		FAIL();
 		return;
 	}
 
-	json_object *expected = json_object_from_file(json.c_str());
-	ASSERT_NE(expected, nullptr);
+	json_object *expected = json_object_from_file(info->json_out);
+	EXPECT_NE(expected, nullptr);
 	if (expected == nullptr) {
+		free(buffer);
+		free_file_info(info);
 		const char *str = json_object_to_json_string(ir);
 
 		const char *expected_str = json_object_to_json_string(expected);
@@ -146,9 +253,10 @@
 	}
 
 	EXPECT_TRUE(json_object_equal(ir, expected));
-
+	free(buffer);
 	json_object_put(ir);
 	json_object_put(expected);
+	free_file_info(info);
 }
 
 //Tests a single randomly generated CPER section of the given type to ensure CPER-JSON IR validity.
@@ -176,18 +284,32 @@
 	int valid = schema_validate_from_file(ir, single_section,
 					      /*all_valid_bits*/ 1);
 	json_object_put(ir);
-	EXPECT_TRUE(valid)
+	EXPECT_GE(valid, 0)
 		<< "IR validation test failed (single section mode = "
 		<< single_section << ")\n";
 }
 
-std::string to_hex(char *input, size_t size)
+int to_hex(const unsigned char *input, size_t size, char **out)
 {
-	std::string out;
-	for (char c : std::span<unsigned char>((unsigned char *)input, size)) {
-		out += std::format("{:02x}", static_cast<unsigned char>(c));
+	*out = (char *)malloc(size * 2);
+	if (out == NULL) {
+		return -1;
 	}
-	return out;
+	int out_index = 0;
+	for (size_t i = 0; i < size; i++) {
+		unsigned char c = input[i];
+		char hex_str[3];
+		int n = snprintf(hex_str, sizeof(hex_str), "%02x", c);
+		if (n != 2) {
+			printf("snprintf2 failed with code %d\n", n);
+			return -1;
+		}
+		(*out)[out_index] = hex_str[0];
+		out_index++;
+		(*out)[out_index] = hex_str[1];
+		out_index++;
+	}
+	return out_index;
 }
 
 //Checks for binary round-trip equality for a given randomly generated CPER record.
@@ -224,13 +346,23 @@
 	}
 	fclose(stream);
 
-	std::cout << "size: " << size << ", cper_buf_size: " << cper_buf_size
-		  << std::endl;
-	EXPECT_EQ(to_hex(buf, size),
-		  to_hex(cper_buf, std::min(size, cper_buf_size)))
-		<< "Binary output was not identical to input (single section mode = "
-		<< single_section << ").";
+	printf("size: %zu, cper_buf_size: %zu\n", size, cper_buf_size);
 
+	char *buf_hex;
+	int buf_hex_len = to_hex((unsigned char *)buf, size, &buf_hex);
+	char *cper_buf_hex;
+	int cper_buf_hex_len =
+		to_hex((unsigned char *)cper_buf, cper_buf_size, &cper_buf_hex);
+
+	EXPECT_EQ(buf_hex_len, cper_buf_hex_len);
+	if (buf_hex_len == cper_buf_hex_len) {
+		EXPECT_EQ(memcmp(buf_hex, cper_buf_hex, buf_hex_len), 0)
+			<< "Binary output was not identical to input (single section mode = "
+			<< single_section << ").";
+	}
+
+	free(cper_buf_hex);
+	free(buf_hex);
 	//Free everything up.
 	fclose(record);
 	free(buf);
@@ -261,7 +393,7 @@
 {
 	for (size_t i = 0; i < section_definitions_len; i++) {
 		//If a conversion one way exists, a conversion the other way must exist.
-		std::string err =
+		const char *err =
 			"If a CPER conversion exists one way, there must be an equivalent method in reverse.";
 		if (section_definitions[i].ToCPER != NULL) {
 			ASSERT_NE(section_definitions[i].ToIR, nullptr) << err;
diff --git a/tests/meson.build b/tests/meson.build
index 2f1e2b2..0123dc2 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -9,7 +9,6 @@
             dependencies: [
                 dependency('threads'),
                 gtest_proj.dependency('gtest'),
-                gtest_proj.dependency('gtest_main'),
             ],
         )
         gmock = gtest_proj.dependency('gmock')
@@ -21,9 +20,12 @@
     endif
 endif
 
-jsonc_daccord = dependency('jsoncdac')
+jsonc_daccord = dependency(
+    'jsoncdac',
+    default_options: ['default_library=static'],
+)
 
-test_sources = ['test-utils.cpp', 'base64_test.cpp']
+test_sources = ['test-utils.c', 'base64_test.cpp']
 
 test_include_dirs = ['.', '../include']
 
@@ -33,7 +35,6 @@
     test_sources,
     implicit_include_directories: false,
     include_directories: include_directories(test_include_dirs),
-    cpp_args: '-fpermissive',
     dependencies: [
         json_c_dep,
         jsonc_daccord,
@@ -43,7 +44,7 @@
         gmock,
     ],
 )
-test('test-cper-tests', cper_tests)
+test('test-cper-tests', cper_tests, protocol: 'gtest')
 
 cxx = meson.get_compiler('cpp')
 
@@ -52,20 +53,20 @@
 if (cxx.get_id() == 'clang') and get_option('fuzz').allowed() and not is_darwin
     sanitize = ['fuzzer']
     fuzz_args = [
-        '-fsanitize=' + ','.join(sanitize),
         '-DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION',
+        '-fsanitize=fuzzer,address,leak',
     ]
 
     foreach fuzzer_test : ['fuzz_cper_buf_to_ir']
         fuzz_exe = executable(
             fuzzer_test,
-            [fuzzer_test + '.cpp'] + libcper_parse_sources + edk_sources + test_sources + libcper_generate_sources,
+            [fuzzer_test + '.c'] + libcper_parse_sources + edk_sources + 'test-utils.c' + libcper_generate_sources,
             implicit_include_directories: false,
             include_directories: include_directories(test_include_dirs),
             cpp_args: fuzz_args,
             c_args: fuzz_args,
             link_args: fuzz_args,
-            dependencies: [json_c_dep, jsonc_daccord, gtest, gmock],
+            dependencies: [json_c_dep, jsonc_daccord],
         )
         test(
             fuzzer_test,
diff --git a/tests/test-utils.c b/tests/test-utils.c
new file mode 100644
index 0000000..1b2adac
--- /dev/null
+++ b/tests/test-utils.c
@@ -0,0 +1,248 @@
+/**
+ * Defines utility functions for testing CPER-JSON IR output from the cper-parse library.
+ *
+ * Author: Lawrence.Tang@arm.com
+ **/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "test-utils.h"
+
+#include <libcper/BaseTypes.h>
+#include <libcper/generator/cper-generate.h>
+
+#include <jsoncdaccord.h>
+#include <json.h>
+#include <libcper/log.h>
+
+// Objects that have mutually exclusive fields (and thereforce can't have both
+// required at the same time) can be added to this list.
+// Truly optional properties that shouldn't be added to "required" field for
+// validating the entire schema with validationbits=1
+// In most cases making sure examples set all valid bits is preferable to adding to this list
+static const char *optional_props[] = {
+	// Some sections don't parse header correctly?
+	"header",
+
+	// Each section is optional
+	"GenericProcessor", "Ia32x64Processor", "ArmProcessor", "Memory",
+	"Memory2", "Pcie", "PciBus", "PciComponent", "Firmware", "GenericDmar",
+	"VtdDmar", "IommuDmar", "CcixPer", "CxlProtocol", "CxlComponent",
+	"Nvidia", "Ampere", "Unknown",
+
+	// CXL?  might have a bug?
+	"partitionID",
+
+	// CXL protocol
+	"capabilityStructure", "deviceSerial",
+
+	// CXL component
+	"cxlComponentEventLog", "addressSpace", "errorType",
+	"participationType", "timedOut", "level", "operation", "preciseIP",
+	"restartableIP", "overflow", "uncorrected", "transactionType",
+
+	// PCIe AER
+	"addressSpace", "errorType", "participationType", "timedOut", "level",
+	"operation", "preciseIP", "restartableIP", "overflow", "uncorrected",
+	"transactionType"
+};
+
+//Returns a ready-for-use memory stream containing a CPER record with the given sections inside.
+FILE *generate_record_memstream(const char **types, UINT16 num_types,
+				char **buf, size_t *buf_size,
+				int single_section,
+				GEN_VALID_BITS_TEST_TYPE validBitsType)
+{
+	//Open a memory stream.
+	FILE *stream = open_memstream(buf, buf_size);
+
+	//Generate a section to the stream, close & return.
+	if (!single_section) {
+		generate_cper_record((char **)(types), num_types, stream,
+				     validBitsType);
+	} else {
+		generate_single_section_record((char *)(types[0]), stream,
+					       validBitsType);
+	}
+	fclose(stream);
+
+	//Return fmemopen() buffer for reading.
+	return fmemopen(*buf, *buf_size, "r");
+}
+
+int iterate_make_required_props(json_object *jsonSchema, int all_valid_bits)
+{
+	//properties
+	json_object *properties =
+		json_object_object_get(jsonSchema, "properties");
+
+	if (properties != NULL) {
+		json_object *requrired_arr = json_object_new_array();
+
+		json_object_object_foreach(properties, property_name,
+					   property_value)
+		{
+			(void)property_value;
+			int add_to_required = 1;
+			size_t num = sizeof(optional_props) /
+				     sizeof(optional_props[0]);
+			for (size_t i = 0; i < num; i++) {
+				if (strcmp(optional_props[i], property_name) ==
+				    0) {
+					add_to_required = 0;
+					break;
+				}
+			}
+
+			if (add_to_required) {
+				//Add to list if property is not optional
+				json_object_array_add(
+					requrired_arr,
+					json_object_new_string(property_name));
+			}
+		}
+
+		json_object_object_foreach(properties, property_name2,
+					   property_value2)
+		{
+			(void)property_name2;
+			if (iterate_make_required_props(property_value2,
+							all_valid_bits) < 0) {
+				json_object_put(requrired_arr);
+				return -1;
+			}
+		}
+
+		if (all_valid_bits) {
+			json_object_object_add(jsonSchema, "required",
+					       requrired_arr);
+		} else {
+			json_object_put(requrired_arr);
+		}
+	}
+
+	// ref
+	json_object *ref = json_object_object_get(jsonSchema, "$ref");
+	if (ref != NULL) {
+		const char *ref_str = json_object_get_string(ref);
+		if (ref_str != NULL) {
+			if (strlen(ref_str) < 1) {
+				cper_print_log("Failed seek filepath: %s\n",
+					       ref_str);
+				return -1;
+			}
+			size_t size =
+				strlen(LIBCPER_JSON_SPEC) + strlen(ref_str);
+			char *path = (char *)malloc(size);
+			int n = snprintf(path, size, "%s%s", LIBCPER_JSON_SPEC,
+					 ref_str + 1);
+			if (n != (int)size - 1) {
+				cper_print_log("Failed concat filepath: %s\n",
+					       ref_str);
+				free(path);
+				return -1;
+			}
+			json_object *ref_obj = json_object_from_file(path);
+			free(path);
+			if (ref_obj == NULL) {
+				cper_print_log("Failed to parse file: %s\n",
+					       ref_str);
+				return -1;
+			}
+
+			if (iterate_make_required_props(ref_obj,
+							all_valid_bits) < 0) {
+				json_object_put(ref_obj);
+				return -1;
+			}
+
+			json_object_object_foreach(ref_obj, key, val)
+			{
+				json_object_object_add(jsonSchema, key,
+						       json_object_get(val));
+			}
+			json_object_object_del(jsonSchema, "$ref");
+
+			json_object_put(ref_obj);
+		}
+	}
+
+	//oneOf
+	const json_object *oneOf = json_object_object_get(jsonSchema, "oneOf");
+	if (oneOf != NULL) {
+		size_t num_elements = json_object_array_length(oneOf);
+
+		for (size_t i = 0; i < num_elements; i++) {
+			json_object *obj = json_object_array_get_idx(oneOf, i);
+			if (iterate_make_required_props(obj, all_valid_bits) <
+			    0) {
+				return -1;
+			}
+		}
+	}
+
+	//items
+	const json_object *items = json_object_object_get(jsonSchema, "items");
+	if (items != NULL) {
+		json_object_object_foreach(items, key, val)
+		{
+			(void)key;
+			if (iterate_make_required_props(val, all_valid_bits) <
+			    0) {
+				return -1;
+			}
+		}
+	}
+
+	return 1;
+}
+
+int schema_validate_from_file(json_object *to_test, int single_section,
+			      int all_valid_bits)
+{
+	const char *schema_file;
+	if (single_section) {
+		schema_file = "cper-json-section-log.json";
+	} else {
+		schema_file = "cper-json-full-log.json";
+	}
+	int size = strlen(schema_file) + 1 + strlen(LIBCPER_JSON_SPEC) + 1;
+	char *schema_path = malloc(size);
+	snprintf(schema_path, size, "%s/%s", LIBCPER_JSON_SPEC, schema_file);
+
+	json_object *schema = json_object_from_file(schema_path);
+
+	if (schema == NULL) {
+		cper_print_log("Could not parse schema file: %s", schema_path);
+		free(schema_path);
+		return 0;
+	}
+
+	if (iterate_make_required_props(schema, all_valid_bits) < 0) {
+		cper_print_log("Failed to make required props\n");
+		json_object_put(schema);
+		free(schema_path);
+		return -1;
+	}
+
+	int err = jdac_validate(to_test, schema);
+	if (err == JDAC_ERR_VALID) {
+		cper_print_log("validation ok\n");
+		json_object_put(schema);
+		free(schema_path);
+		return 1;
+	}
+
+	cper_print_log("validate failed %d: %s\n", err, jdac_errorstr(err));
+
+	cper_print_log("schema: \n%s\n",
+		       json_object_to_json_string_ext(schema,
+						      JSON_C_TO_STRING_PRETTY));
+	cper_print_log("to_test: \n%s\n",
+		       json_object_to_json_string_ext(to_test,
+						      JSON_C_TO_STRING_PRETTY));
+	json_object_put(schema);
+	free(schema_path);
+	return 0;
+}
diff --git a/tests/test-utils.cpp b/tests/test-utils.cpp
deleted file mode 100644
index e4d79d3..0000000
--- a/tests/test-utils.cpp
+++ /dev/null
@@ -1,229 +0,0 @@
-/**
- * Defines utility functions for testing CPER-JSON IR output from the cper-parse library.
- *
- * Author: Lawrence.Tang@arm.com
- **/
-
-#include <cstdio>
-#include <cstdlib>
-#include <fstream>
-#include <map>
-#include <filesystem>
-#include <vector>
-#include <algorithm>
-#include "test-utils.hpp"
-
-#include <libcper/BaseTypes.h>
-#include <libcper/generator/cper-generate.h>
-
-extern "C" {
-#include <jsoncdaccord.h>
-#include <json.h>
-#include <libcper/log.h>
-}
-
-namespace fs = std::filesystem;
-
-// Objects that have mutually exclusive fields (and thereforce can't have both
-// required at the same time) can be added to this list.
-// Truly optional properties that shouldn't be added to "required" field for
-// validating the entire schema with validationbits=1
-// In most cases making sure examples set all valid bits is preferable to adding to this list
-const static std::vector<std::string> optional_props = {
-	{ // Some sections don't parse header correctly?
-	  "header",
-
-	  // Each section is optional
-	  "GenericProcessor", "Ia32x64Processor", "ArmProcessor", "Memory",
-	  "Memory2", "Pcie", "PciBus", "PciComponent", "Firmware",
-	  "GenericDmar", "VtdDmar", "IommuDmar", "CcixPer", "CxlProtocol",
-	  "CxlComponent", "Nvidia", "Ampere", "Unknown",
-
-	  // CXL?  might have a bug?
-	  "partitionID",
-
-	  // CXL protocol
-	  "capabilityStructure", "deviceSerial",
-
-	  // CXL component
-	  "cxlComponentEventLog", "addressSpace", "errorType",
-	  "participationType", "timedOut", "level", "operation", "preciseIP",
-	  "restartableIP", "overflow", "uncorrected", "transactionType",
-
-	  // PCIe AER
-	  "addressSpace", "errorType", "participationType", "timedOut", "level",
-	  "operation", "preciseIP", "restartableIP", "overflow", "uncorrected",
-	  "transactionType" }
-};
-
-//Returns a ready-for-use memory stream containing a CPER record with the given sections inside.
-FILE *generate_record_memstream(const char **types, UINT16 num_types,
-				char **buf, size_t *buf_size,
-				int single_section,
-				GEN_VALID_BITS_TEST_TYPE validBitsType)
-{
-	//Open a memory stream.
-	FILE *stream = open_memstream(buf, buf_size);
-
-	//Generate a section to the stream, close & return.
-	if (!single_section) {
-		generate_cper_record(const_cast<char **>(types), num_types,
-				     stream, validBitsType);
-	} else {
-		generate_single_section_record(const_cast<char *>(types[0]),
-					       stream, validBitsType);
-	}
-	fclose(stream);
-
-	//Return fmemopen() buffer for reading.
-	return fmemopen(*buf, *buf_size, "r");
-}
-
-int iterate_make_required_props(json_object *jsonSchema, bool all_valid_bits)
-{
-	//properties
-	json_object *properties =
-		json_object_object_get(jsonSchema, "properties");
-
-	if (properties != nullptr) {
-		json_object *requrired_arr = json_object_new_array();
-
-		json_object_object_foreach(properties, property_name,
-					   property_value)
-		{
-			bool add_to_required = true;
-			const auto it_find_opt_prop = std::ranges::find(
-				optional_props, property_name);
-			if (it_find_opt_prop != optional_props.end()) {
-				add_to_required = false;
-			}
-
-			if (add_to_required) {
-				//Add to list if property is not optional
-				json_object_array_add(
-					requrired_arr,
-					json_object_new_string(property_name));
-			}
-		}
-
-		json_object_object_foreach(properties, property_name2,
-					   property_value2)
-		{
-			(void)property_name2;
-			if (iterate_make_required_props(property_value2,
-							all_valid_bits) < 0) {
-				return -1;
-			}
-		}
-
-		if (all_valid_bits) {
-			json_object_object_add(jsonSchema, "required",
-					       requrired_arr);
-		}
-		//json_object_put(requrired_arr);
-	}
-
-	// ref
-	json_object *ref = json_object_object_get(jsonSchema, "$ref");
-	if (ref != nullptr) {
-		const char *ref_str = json_object_get_string(ref);
-		if (ref_str != nullptr) {
-			std::string ref_path = LIBCPER_JSON_SPEC;
-			// remove the leading .
-			ref_path += std::string(ref_str).substr(1);
-			json_object *ref_obj =
-				json_object_from_file(ref_path.c_str());
-			if (ref_obj == nullptr) {
-				printf("Failed to parse file: %s\n",
-				       ref_path.c_str());
-				return -1;
-			}
-
-			if (iterate_make_required_props(ref_obj,
-							all_valid_bits) < 0) {
-				json_object_put(ref_obj);
-				return -1;
-			}
-
-			json_object_object_foreach(ref_obj, key, val)
-			{
-				json_object_object_add(jsonSchema, key,
-						       json_object_get(val));
-			}
-			json_object_object_del(jsonSchema, "$ref");
-
-			json_object_put(ref_obj);
-		}
-	}
-
-	//oneOf
-	const json_object *oneOf = json_object_object_get(jsonSchema, "oneOf");
-	if (oneOf != nullptr) {
-		size_t num_elements = json_object_array_length(oneOf);
-
-		for (size_t i = 0; i < num_elements; i++) {
-			json_object *obj = json_object_array_get_idx(oneOf, i);
-			if (iterate_make_required_props(obj, all_valid_bits) <
-			    0) {
-				return -1;
-			}
-		}
-	}
-
-	//items
-	const json_object *items = json_object_object_get(jsonSchema, "items");
-	if (items != nullptr) {
-		json_object_object_foreach(items, key, val)
-		{
-			(void)key;
-			if (iterate_make_required_props(val, all_valid_bits) <
-			    0) {
-				return -1;
-			}
-		}
-	}
-
-	return 1;
-}
-
-int schema_validate_from_file(json_object *to_test, int single_section,
-			      int all_valid_bits)
-{
-	const char *schema_file;
-	if (single_section) {
-		schema_file = "cper-json-section-log.json";
-	} else {
-		schema_file = "cper-json-full-log.json";
-	}
-
-	std::string schema_path = LIBCPER_JSON_SPEC;
-	schema_path += "/";
-	schema_path += schema_file;
-
-	json_object *schema = json_object_from_file(schema_path.c_str());
-	if (schema == nullptr) {
-		cper_print_log("Could not parse schema file: %s", schema_file);
-		return 0;
-	}
-
-	if (iterate_make_required_props(schema, all_valid_bits) < 0) {
-		printf("Failed to make required props\n");
-		json_object_put(schema);
-		return -1;
-	}
-
-	int err = jdac_validate(to_test, schema);
-	if (err == JDAC_ERR_VALID) {
-		printf("validation ok\n");
-		json_object_put(schema);
-		return 1;
-	}
-	printf("validate failed %d: %s\n", err, jdac_errorstr(err));
-
-	printf("schema: \n%s\n",
-	       json_object_to_json_string_ext(schema, JSON_C_TO_STRING_PRETTY));
-	printf("to_test: \n%s\n", json_object_to_json_string_ext(
-					  to_test, JSON_C_TO_STRING_PRETTY));
-	json_object_put(schema);
-	return 0;
-}
diff --git a/tests/test-utils.hpp b/tests/test-utils.h
similarity index 84%
rename from tests/test-utils.hpp
rename to tests/test-utils.h
index 3b1d061..a7fb723 100644
--- a/tests/test-utils.hpp
+++ b/tests/test-utils.h
@@ -1,6 +1,10 @@
 #ifndef CPER_IR_TEST_UTILS_H
 #define CPER_IR_TEST_UTILS_H
 
+#ifdef __cplusplus
+extern "C" {
+#endif
+
 #include <stdio.h>
 #include <libcper/BaseTypes.h>
 #include <libcper/generator/sections/gen-section.h>
@@ -9,7 +13,7 @@
 // Controls whether required properties are added to the majority of property
 // definitions.  This is useful for unit tests that are validating JSON where
 // all fields are valid
-enum class AddRequiredProps { YES, NO };
+enum AddRequiredProps { AddRequired, NoModify };
 
 FILE *generate_record_memstream(const char **types, UINT16 num_types,
 				char **buf, size_t *buf_size,
@@ -19,4 +23,8 @@
 int schema_validate_from_file(json_object *to_test, int single_section,
 			      int all_valid_bits);
 
+#ifdef __cplusplus
+}
+#endif
+
 #endif