diff --git a/aux_progs/CMakeLists.txt b/aux_progs/CMakeLists.txt index 8c122c1b..3370880d 100644 --- a/aux_progs/CMakeLists.txt +++ b/aux_progs/CMakeLists.txt @@ -1,5 +1,5 @@ -add_executable(gmerge gmerge.c uint8.c) +add_executable(gmerge gmerge.c rd_msg.c uint8.c) add_executable(smallest_grib2 smallest_grib2.c uint8.c) diff --git a/aux_progs/gmerge.c b/aux_progs/gmerge.c index d2919deb..81c9c8b8 100644 --- a/aux_progs/gmerge.c +++ b/aux_progs/gmerge.c @@ -13,6 +13,7 @@ * 12/2022 | W. Ebisuzaki | better error messages, list of input files can be 1 file * 01/2023 | W. Ebisuzaki | updated for 2023, cmake compile added * 05/2025 | W. Ebisuzaki | increase N again (32..200..system limit) + * 12/2025 | A. Stahl | Moved rd_msg() to rd_msg.c to support testing * * @author Public Domain: Wesley Ebisuzaki @date 05/2009 */ @@ -24,7 +25,6 @@ /** Current Version of gmerge */ #define VERSION "gmerge v1.6 5/2025" -unsigned long int uint8(unsigned char *); int rd_msg(FILE *, FILE *); /** @@ -108,45 +108,3 @@ int main(int argc, char **argv) { exit(0); } -/** Maximum Buffer Size */ -#define BSIZE 4096*8 - -/** - * Reads a GRIB2 message from the input file and writes it to the output file. - * - * @param in Pointer to the input file. - * @param out Pointer to the output file. - * - * @return 0 on success, non-zero on error. - * - * @author Wesley Ebisuzaki @date 05/2009 - */ -int rd_msg(FILE *in, FILE *out) { - long unsigned int n; - int i,j,k; - unsigned char header[BSIZE]; - - if (feof(in)) return -1; - - i = fread(header, 1, 16, in); - if (i != 16) return -1; - if (header[0] != 'G' || header[1] != 'R' || header[2] != 'I' || - header[3] != 'B') return -1; - - n = uint8(&(header[8])); - - j = n < BSIZE ? n : BSIZE; - k = fread(header+16,1,j-16,in); - if (k != j-16) return -1; - - fwrite(header,1,j,out); - n -= j; - - while (n) { - j = n < BSIZE ? n : BSIZE; - k = fread(header,1,j,in); - fwrite(header,1,j,out); - n -= j; - } - return 0; -} diff --git a/aux_progs/rd_msg.c b/aux_progs/rd_msg.c new file mode 100644 index 00000000..78e5179d --- /dev/null +++ b/aux_progs/rd_msg.c @@ -0,0 +1,59 @@ +/** @file + * @brief Reads a GRIB2 message from input file and writes it to output file. Supports gmerge.c. + * + * ### Program History Log + * Date | Programmer | Comments + * -----|------------|--------- + * 05/2009 | W. Ebisuzaki | Initial + * 12/2025 | A. Stahl | Moved from gmerge.c to support testing + * @author Public Domain: Wesley Ebisuzaki @date 05/2009 + */ + +#include +#include +#include + +unsigned long int uint8(unsigned char *); + +/** Maximum Buffer Size */ +#define BSIZE 4096*8 + +/** + * Reads a GRIB2 message from the input file and writes it to the output file. + * + * @param in Pointer to the input file. + * @param out Pointer to the output file. + * + * @return 0 on success, non-zero on error. + * + * @author Wesley Ebisuzaki @date 05/2009 + */ +int rd_msg(FILE *in, FILE *out) { + long unsigned int n; + int i,j,k; + unsigned char header[BSIZE]; + + if (feof(in)) return -1; + + i = fread(header, 1, 16, in); + if (i != 16) return -1; + if (header[0] != 'G' || header[1] != 'R' || header[2] != 'I' || + header[3] != 'B') return -1; + + n = uint8(&(header[8])); + + j = n < BSIZE ? n : BSIZE; + k = fread(header+16,1,j-16,in); + if (k != j-16) return -1; + + fwrite(header,1,j,out); + n -= j; + + while (n) { + j = n < BSIZE ? n : BSIZE; + k = fread(header,1,j,in); + fwrite(header,1,j,out); + n -= j; + } + return 0; +} diff --git a/aux_progs/uint8.c b/aux_progs/uint8.c index 103c1c75..d9513ba7 100644 --- a/aux_progs/uint8.c +++ b/aux_progs/uint8.c @@ -8,6 +8,24 @@ #include #include "grb2.h" +/** For unit tests only. Sets ULONG_MAX to 32-bit max if FORCE_32BIT_TEST is defined. */ +#ifdef FORCE_32BIT_TEST +#undef ULONG_MAX +#define ULONG_MAX 4294967295UL +#endif + +/** + * Converts an 8-byte unsigned integer from a byte array to an unsigned long integer. + * + * @param p Pointer to an array of at least 8 bytes representing the unsigned integer in big-endian order. + * + * @return The converted unsigned long integer. + * + * @note On 32-bit systems, if the value exceeds the maximum representable value of unsigned long int, + * the program will print an error message and exit. + * + * @author Wesley Ebisuzaki @date 2006 + */ unsigned long int uint8(unsigned char *p) { #if (ULONG_MAX == 4294967295UL) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index e336fb83..3c85a0f3 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -191,6 +191,29 @@ copy_test_data(ref_jpeg2simple.txt) copy_test_data(LARGECAT220250305_12_1443copy.grib2) copy_test_data(ref_LARGECAT220250305_12_1443copy_stats.txt) +# Build and run tests for auxillary programs. + +if(CMAKE_SIZEOF_VOID_P EQUAL 8) + # Test normal 64-bit behavior + add_executable(test_uint8_64bit test_uint8_64bit.c ${CMAKE_SOURCE_DIR}/aux_progs/uint8.c) + target_include_directories(test_uint8_64bit PRIVATE ${CMAKE_SOURCE_DIR}/aux_progs) + add_test(NAME test_uint8_64bit COMMAND test_uint8_64bit) + + # Test 32-bit behavior on 64-bit system using FORCE_32BIT_TEST + add_executable(test_uint8_32bit test_uint8_32bit.c ${CMAKE_SOURCE_DIR}/aux_progs/uint8.c) + target_include_directories(test_uint8_32bit PRIVATE ${CMAKE_SOURCE_DIR}/aux_progs) + target_compile_definitions(test_uint8_32bit PRIVATE FORCE_32BIT_TEST) + add_test(NAME test_uint8_32bit COMMAND test_uint8_32bit) +else() + add_executable(test_uint8_32bit test_uint8_32bit.c ${CMAKE_SOURCE_DIR}/aux_progs/uint8.c) + target_include_directories(test_uint8_32bit PRIVATE ${CMAKE_SOURCE_DIR}/aux_progs) + add_test(NAME test_uint8_32bit COMMAND test_uint8_32bit) +endif() + +add_executable(test_rd_msg test_rd_msg.c ${CMAKE_SOURCE_DIR}/aux_progs/rd_msg.c + ${CMAKE_SOURCE_DIR}/aux_progs/uint8.c) +target_include_directories(test_rd_msg PRIVATE ${CMAKE_SOURCE_DIR}/aux_progs) +add_test(NAME test_rd_msg COMMAND test_rd_msg) if (MAKE_FTN_API) build_ftn_api_test(test_fortran_api) diff --git a/tests/test_rd_msg.c b/tests/test_rd_msg.c new file mode 100644 index 00000000..44c01e09 --- /dev/null +++ b/tests/test_rd_msg.c @@ -0,0 +1,164 @@ +/** + * This is a test for the gmerge helper function rd_msg(). + * + * Alyson Stahl + */ + +#include +#include +#include + +int rd_msg(FILE *in, FILE *out); + +int +main() +{ + printf("Testing gmerge helper function rd_msg()...\n"); + printf("EOF on first read. Should return -1."); + { + FILE *input, *output; + int result; + + // Create empty file + input = fopen("test_empty.tmp", "wb"); + fclose(input); + + input = fopen("test_empty.tmp", "rb"); + output = fopen("test_output.tmp", "wb"); + result = rd_msg(input, output); + + fclose(input); + fclose(output); + + remove("test_empty.tmp"); + remove("test_output.tmp"); + + if (result != -1) return 1; + } + printf("ok!\n"); + printf("Invalid GRIB header. Should return -1."); + { + FILE *input, *output; + unsigned char bad_data[16]; + int result; + + memset(bad_data, 0, 16); + strcpy((char*)bad_data, "BADHEADER"); + + input = fopen("test_bad.tmp", "wb"); + fwrite(bad_data, 1, 16, input); + fclose(input); + + input = fopen("test_bad.tmp", "rb"); + output = fopen("test_output.tmp", "wb"); + + result = rd_msg(input, output); + + fclose(input); + fclose(output); + remove("test_bad.tmp"); + remove("test_output.tmp"); + + if (result != -1) return 2; + } + printf("ok!\n"); + printf("Message too large (> 1GB). Should return -1."); + { + FILE *input, *output; + unsigned char large_header[16]; + int result; + + memset(large_header, 0, 16); + strcpy((char*)large_header, "GRIB"); + // Set message length to > 1GB + large_header[8] = 0x00; + large_header[9] = 0x00; + large_header[10] = 0x00; + large_header[11] = 0x40; // 1GB = 0x40000000 + large_header[12] = 0x00; + large_header[13] = 0x00; + large_header[14] = 0x00; + large_header[15] = 0x00; + + input = fopen("test_large.tmp", "wb"); + fwrite(large_header, 1, 16, input); + fclose(input); + + input = fopen("test_large.tmp", "rb"); + output = fopen("test_output.tmp", "wb"); + + result = rd_msg(input, output); + + fclose(input); + fclose(output); + remove("test_large.tmp"); + remove("test_output.tmp"); + + if (result != -1) return 3; + } + printf("ok!\n"); + printf("Incomplete message read. Should return -1."); + { + FILE *input, *output; + unsigned char incomplete_header[16]; + int result; + + memset(incomplete_header, 0, 16); + strcpy((char*)incomplete_header, "GRIB"); + // Set message length to 32 bytes + incomplete_header[15] = 32; + + input = fopen("test_incomplete.tmp", "wb"); + fwrite(incomplete_header, 1, 16, input); + // Write only 8 more bytes instead of 16 + unsigned char partial_data[8] = {0}; + fwrite(partial_data, 1, 8, input); + fclose(input); + + input = fopen("test_incomplete.tmp", "rb"); + output = fopen("test_output.tmp", "wb"); + + result = rd_msg(input, output); + + fclose(input); + fclose(output); + remove("test_incomplete.tmp"); + remove("test_output.tmp"); + + if (result != -1) return 4; + } + printf("ok!\n"); + printf("Valid GRIB message. Should return 0."); + { + FILE *input, *output; + unsigned char valid_header[16]; + int result; + + memset(valid_header, 0, 16); + strcpy((char*)valid_header, "GRIB"); + // Set message length to 32 bytes + valid_header[15] = 32; + + input = fopen("test_valid.tmp", "wb"); + fwrite(valid_header, 1, 16, input); + // Write 16 more bytes of data + unsigned char data[16] = {0}; + fwrite(data, 1, 16, input); + fclose(input); + + input = fopen("test_valid.tmp", "rb"); + output = fopen("test_output.tmp", "wb"); + + result = rd_msg(input, output); + + fclose(input); + fclose(output); + remove("test_valid.tmp"); + remove("test_output.tmp"); + + if (result != 0) return 5; + } + printf("ok!\n"); + printf("SUCCESS!\n"); + return 0; +} \ No newline at end of file diff --git a/tests/test_uint8_32bit.c b/tests/test_uint8_32bit.c new file mode 100644 index 00000000..008e9ac0 --- /dev/null +++ b/tests/test_uint8_32bit.c @@ -0,0 +1,112 @@ +/** + * This is a test for uint8() function in aux_progs. For 32-bit systems. + * + * Alyson Stahl + */ + +#include +#include +#include +#include +#include +#include +#include "grb2.h" + +#ifdef FORCE_32BIT_TEST +#undef ULONG_MAX +#define ULONG_MAX 4294967295UL +#endif + +unsigned long int uint8(unsigned char *p); + +// Jump buffer for catching exit() calls +static jmp_buf exit_jmp; +static int exit_code = 0; + +// Override exit() to catch it during testing +void exit(int code) { + exit_code = code; + longjmp(exit_jmp, 1); +} + +int +main() +{ + printf("Testing uint8() function for 32-bit systems...\n"); + printf("Verifying we are in 32-bit test mode."); + { + if (ULONG_MAX != 4294967295UL) { + printf("FAIL: ULONG_MAX should be 4294967295 for 32-bit test\n"); + return 1; + } + } + printf("ok!\n"); + printf("Testing error case: upper byte set (should trigger exit)\n"); + { + unsigned char test_bytes[8]; + + memset(test_bytes, 0, 8); + test_bytes[0] = 0x01; // Set upper byte - this should cause exit(8) + + // Set up jump point to catch the exit() + if (setjmp(exit_jmp) == 0) { + // First time through - call the function that should exit + uint8(test_bytes); + // If we get here, the function didn't exit as expected + printf("FAIL: Error case test - function should have called exit() but didn't\n"); + return 2; + } else { + // We jumped back here from exit() - check the exit code + if (exit_code != 8) { + printf("FAIL: Error case test - called exit(%d), expected exit(8)\n", exit_code); + return 3; + } + } + } + printf("ok!\n"); + printf("Test with all zeros. Should work.\n"); + { + unsigned char test_bytes[8]; + unsigned long result; + + memset(test_bytes, 0, 8); + result = uint8(test_bytes); + if (result != 0) { + printf("FAIL: All zeros test - expected 0, got %lu\n", result); + return 4; + } + } + printf("ok!\n"); + printf("Test simple pattern in lower 4 bytes. Should work.\n"); + { + unsigned char test_bytes[8]; + unsigned long result; + + memset(test_bytes, 0, 8); + test_bytes[4] = 0x12; test_bytes[5] = 0x34; test_bytes[6] = 0x56; test_bytes[7] = 0x78; + result = uint8(test_bytes); + + unsigned long expected = (0x12UL << 24) + (0x34UL << 16) + (0x56UL << 8) + 0x78UL; + if (result != expected) { + printf("FAIL: Simple pattern test - expected %lu, got %lu\n", expected, result); + return 5; + } + } + printf("ok!\n"); + printf("Test max 32-bit value. Should work.\n"); + { + unsigned char test_bytes[8]; + unsigned long result; + + memset(test_bytes, 0, 4); // Upper 4 bytes zero + memset(test_bytes + 4, 0xFF, 4); // Lower 4 bytes all 0xFF + result = uint8(test_bytes); + if (result != 4294967295UL) { + printf("FAIL: Max 32-bit value test - expected 4294967295, got %lu\n", result); + return 6; + } + } + printf("ok!\n"); + printf("SUCCESS!\n"); + return 0; +} \ No newline at end of file diff --git a/tests/test_uint8_64bit.c b/tests/test_uint8_64bit.c new file mode 100644 index 00000000..7a6423e7 --- /dev/null +++ b/tests/test_uint8_64bit.c @@ -0,0 +1,126 @@ +/** + * This is a test for uint8() function in aux_progs. For 64-bit systems. + * + * Alyson Stahl + */ + + +#include +#include +#include +#include +#include "grb2.h" + +unsigned long int uint8(unsigned char *p); + +int +main() +{ + printf("Testing uint8() function for 64-bit systems...\n"); + printf("Verifying we are in 64-bit test mode."); + { + if (ULONG_MAX == 4294967295UL) { + printf("FAIL: Expected 64-bit system (ULONG_MAX > 4294967295)\n"); + return 1; + } + } + printf("ok!\n"); + printf("Test with all zeros. Should work."); + { + unsigned char test_bytes[8] = {0, 0, 0, 0, 0, 0, 0, 0}; + unsigned long result; + + result = uint8(test_bytes); + + if (result != 0) { + printf("FAIL: All zeros test - expected 0, got %lu\n", result); + return 2; + } + } + printf("ok!\n"); + printf("Test simple pattern in lower 4 bytes only. Should work."); + { + unsigned char test_bytes[8] = {0x00, 0x00, 0x00, 0x00, 0x12, 0x34, 0x56, 0x78}; + unsigned long result; + unsigned long expected = (0x12UL << 24) + (0x34UL << 16) + (0x56UL << 8) + 0x78UL; + + result = uint8(test_bytes); + + if (result != expected) { + printf("FAIL: Lower 4 bytes pattern test - expected %lu, got %lu\n", expected, result); + return 3; + } + } + printf("ok!\n"); + printf("Test most significant bytes only. Should work."); + { + unsigned char test_bytes[8] = {0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + unsigned long result; + unsigned long expected = (0x01UL << 56); + + result = uint8(test_bytes); + + if (result != expected) { + printf("FAIL: MSB only test - expected %lu, got %lu\n", expected, result); + return 4; + } + } + printf("ok!\n"); + printf("Test all 8 bytes pattern. Should work."); + { + unsigned char test_bytes[8] = {0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF}; + unsigned long result; + unsigned long expected = + (0x01UL << 56) + (0x23UL << 48) + (0x45UL << 40) + (0x67UL << 32) + + (0x89UL << 24) + (0xABUL << 16) + (0xCDUL << 8) + 0xEFUL; + + result = uint8(test_bytes); + + if (result != expected) { + printf("FAIL: All bytes pattern test - expected %lu, got %lu\n", expected, result); + return 5; + } + } + printf("ok!\n"); + printf("Test max 64-bit value. Should work."); + { + unsigned char test_bytes[8] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; + unsigned long result; + unsigned long expected = 18446744073709551615UL; // 2^64 - 1 + + result = uint8(test_bytes); + if (result != expected) { + printf("FAIL: Max 64-bit value test - expected %lu, got %lu\n", expected, result); + return 6; + } + } + printf("ok!\n"); + printf("Test individual byte position (verify bit shifting)."); + { + unsigned char test_bytes[8] = {0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + unsigned long result; + unsigned long expected = (0x01UL << 48); + + result = uint8(test_bytes); + if (result != expected) { + printf("FAIL: Byte position test - expected %lu, got %lu\n", expected, result); + return 7; + } + } + printf("ok!\n"); + printf("Test values that would trigger the 32-bit overflow but are valid on 64-bit"); + { + unsigned char test_bytes[8] = {0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00}; + unsigned long result; + unsigned long expected = (0x01UL << 32); + + result = uint8(test_bytes); + if (result != expected) { + printf("FAIL: 32-bit overflow test - expected %lu, got %lu\n", expected, result); + return 8; + } + } + printf("ok!\n"); + printf("SUCCESS!\n"); + return 0; +} \ No newline at end of file