diff --git a/c/qrcodegen-test.c b/c/qrcodegen-test.c index d886fd2..822d7e9 100644 --- a/c/qrcodegen-test.c +++ b/c/qrcodegen-test.c @@ -26,9 +26,11 @@ #include +#include #include #include #include +#include #include "qrcodegen.h" #define ARRAY_LENGTH(name) (sizeof(name) / sizeof(name[0])) @@ -39,13 +41,91 @@ static int numTestCases = 0; // Prototypes of private functions under test +int getTextProperties(const char *text, bool *isNumeric, bool *isAlphanumeric, int *textBits); int getNumDataCodewords(int version, enum qrcodegen_Ecc ecl); int getNumRawDataModules(int version); +void calcReedSolomonGenerator(int degree, uint8_t result[]); +void calcReedSolomonRemainder(const uint8_t data[], int dataLen, const uint8_t generator[], int degree, uint8_t result[]); uint8_t finiteFieldMultiply(uint8_t x, uint8_t y); +int getAlignmentPatternPositions(int version, uint8_t result[7]); /*---- Test cases ----*/ +static void testSize(void) { + int cases[][2] = { + { 1, 21}, + { 6, 41}, + {20, 97}, + {33, 149}, + {40, 177}, + }; + for (size_t i = 0; i < ARRAY_LENGTH(cases); i++) { + int *tc = cases[i]; + assert(qrcodegen_getSize(tc[0]) == tc[1]); + numTestCases++; + } +} + + +static void testGetTextProperties(void) { + bool isNumeric, isAlphanumeric; + int textLen, textBits; + + textLen = getTextProperties("", &isNumeric, &isAlphanumeric, &textBits); + assert(textLen == 0 && isNumeric && isAlphanumeric && textBits == 0); + numTestCases++; + + textLen = getTextProperties("0", &isNumeric, &isAlphanumeric, &textBits); + assert(textLen == 1 && isNumeric && isAlphanumeric && textBits == 4); + numTestCases++; + + textLen = getTextProperties("768", &isNumeric, &isAlphanumeric, &textBits); + assert(textLen == 3 && isNumeric && isAlphanumeric && textBits == 10); + numTestCases++; + + textLen = getTextProperties("A1", &isNumeric, &isAlphanumeric, &textBits); + assert(textLen == 2 && !isNumeric && isAlphanumeric && textBits == 11); + numTestCases++; + + textLen = getTextProperties("THE: QUICK+/*BROWN$FOX.", &isNumeric, &isAlphanumeric, &textBits); + assert(textLen == 23 && !isNumeric && isAlphanumeric && textBits == 127); + numTestCases++; + + textLen = getTextProperties("aB 9", &isNumeric, &isAlphanumeric, &textBits); + assert(textLen == 4 && !isNumeric && !isAlphanumeric && textBits == 32); + numTestCases++; + + char text[32769]; + + memset(text, '5', sizeof(text)); + text[32768] = '\0'; + textLen = getTextProperties(text, &isNumeric, &isAlphanumeric, &textBits); + assert(textLen < 0); + numTestCases++; + + memset(text, '1', sizeof(text)); + text[32767] = '\0'; + textLen = getTextProperties(text, &isNumeric, &isAlphanumeric, &textBits); + assert((109224L > INT_MAX && textLen < 0) || + (109224L <= INT_MAX && textLen == 32767 && isNumeric && isAlphanumeric && textBits == 109224L)); + numTestCases++; + + memset(text, 'a', sizeof(text)); + text[4095] = '\0'; + textLen = getTextProperties(text, &isNumeric, &isAlphanumeric, &textBits); + assert(textLen == 4095 && !isNumeric && !isAlphanumeric && textBits == 32760); + numTestCases++; + + memset(text, 'a', sizeof(text)); + text[32767] = '\0'; + textLen = getTextProperties(text, &isNumeric, &isAlphanumeric, &textBits); + assert((262136L > INT_MAX && textLen < 0) || + (262136L <= INT_MAX && textLen == 32767 && !isNumeric && !isAlphanumeric && textBits == 262136L)); + numTestCases++; +} + + static void testGetNumDataCodewords(void) { int cases[][3] = { { 3, 1, 44}, @@ -110,6 +190,104 @@ static void testGetNumRawDataModules(void) { } +static void testCalcReedSolomonGenerator(void) { + uint8_t generator[30]; + + calcReedSolomonGenerator(1, generator); + assert(generator[0] == 0x01); + numTestCases++; + + calcReedSolomonGenerator(2, generator); + assert(generator[0] == 0x03); + assert(generator[1] == 0x02); + numTestCases++; + + calcReedSolomonGenerator(5, generator); + assert(generator[0] == 0x1F); + assert(generator[1] == 0xC6); + assert(generator[2] == 0x3F); + assert(generator[3] == 0x93); + assert(generator[4] == 0x74); + numTestCases++; + + calcReedSolomonGenerator(30, generator); + assert(generator[ 0] == 0xD4); + assert(generator[ 1] == 0xF6); + assert(generator[ 5] == 0xC0); + assert(generator[12] == 0x16); + assert(generator[13] == 0xD9); + assert(generator[20] == 0x12); + assert(generator[27] == 0x6A); + assert(generator[29] == 0x96); + numTestCases++; +} + + +static void testCalcReedSolomonRemainder(void) { + { + uint8_t data[1]; + uint8_t generator[3]; + uint8_t remainder[ARRAY_LENGTH(generator)]; + calcReedSolomonGenerator(ARRAY_LENGTH(generator), generator); + calcReedSolomonRemainder(data, 0, generator, ARRAY_LENGTH(generator), remainder); + assert(remainder[0] == 0); + assert(remainder[1] == 0); + assert(remainder[2] == 0); + numTestCases++; + } + { + uint8_t data[2] = {0, 1}; + uint8_t generator[4]; + uint8_t remainder[ARRAY_LENGTH(generator)]; + calcReedSolomonGenerator(ARRAY_LENGTH(generator), generator); + calcReedSolomonRemainder(data, ARRAY_LENGTH(data), generator, ARRAY_LENGTH(generator), remainder); + assert(remainder[0] == generator[0]); + assert(remainder[1] == generator[1]); + assert(remainder[2] == generator[2]); + assert(remainder[3] == generator[3]); + numTestCases++; + } + { + uint8_t data[5] = {0x03, 0x3A, 0x60, 0x12, 0xC7}; + uint8_t generator[5]; + uint8_t remainder[ARRAY_LENGTH(generator)]; + calcReedSolomonGenerator(ARRAY_LENGTH(generator), generator); + calcReedSolomonRemainder(data, ARRAY_LENGTH(data), generator, ARRAY_LENGTH(generator), remainder); + assert(remainder[0] == 0xCB); + assert(remainder[1] == 0x36); + assert(remainder[2] == 0x16); + assert(remainder[3] == 0xFA); + assert(remainder[4] == 0x9D); + numTestCases++; + } + { + uint8_t data[43] = { + 0x38, 0x71, 0xDB, 0xF9, 0xD7, 0x28, 0xF6, 0x8E, 0xFE, 0x5E, + 0xE6, 0x7D, 0x7D, 0xB2, 0xA5, 0x58, 0xBC, 0x28, 0x23, 0x53, + 0x14, 0xD5, 0x61, 0xC0, 0x20, 0x6C, 0xDE, 0xDE, 0xFC, 0x79, + 0xB0, 0x8B, 0x78, 0x6B, 0x49, 0xD0, 0x1A, 0xAD, 0xF3, 0xEF, + 0x52, 0x7D, 0x9A, + }; + uint8_t generator[30]; + uint8_t remainder[ARRAY_LENGTH(generator)]; + calcReedSolomonGenerator(ARRAY_LENGTH(generator), generator); + calcReedSolomonRemainder(data, ARRAY_LENGTH(data), generator, ARRAY_LENGTH(generator), remainder); + assert(remainder[ 0] == 0xCE); + assert(remainder[ 1] == 0xF0); + assert(remainder[ 2] == 0x31); + assert(remainder[ 3] == 0xDE); + assert(remainder[ 8] == 0xE1); + assert(remainder[12] == 0xCA); + assert(remainder[17] == 0xE3); + assert(remainder[19] == 0x85); + assert(remainder[20] == 0x50); + assert(remainder[24] == 0xBE); + assert(remainder[29] == 0xB3); + numTestCases++; + } +} + + static void testFiniteFieldMultiply(void) { uint8_t cases[][3] = { {0x00, 0x00, 0x00}, @@ -137,12 +315,44 @@ static void testFiniteFieldMultiply(void) { } +static void testGetAlignmentPatternPositions(void) { + int cases[][9] = { + { 1, 0, -1, -1, -1, -1, -1, -1, -1}, + { 2, 2, 6, 18, -1, -1, -1, -1, -1}, + { 3, 2, 6, 22, -1, -1, -1, -1, -1}, + { 6, 2, 6, 34, -1, -1, -1, -1, -1}, + { 7, 3, 6, 22, 38, -1, -1, -1, -1}, + { 8, 3, 6, 24, 42, -1, -1, -1, -1}, + {16, 4, 6, 26, 50, 74, -1, -1, -1}, + {25, 5, 6, 32, 58, 84, 110, -1, -1}, + {32, 6, 6, 34, 60, 86, 112, 138, -1}, + {33, 6, 6, 30, 58, 86, 114, 142, -1}, + {39, 7, 6, 26, 54, 82, 110, 138, 166}, + {40, 7, 6, 30, 58, 86, 114, 142, 170}, + }; + for (size_t i = 0; i < ARRAY_LENGTH(cases); i++) { + int *tc = cases[i]; + uint8_t pos[7]; + int num = getAlignmentPatternPositions(tc[0], pos); + assert(num == tc[1]); + for (int j = 0; j < num; j++) + assert(pos[j] == tc[2 + j]); + numTestCases++; + } +} + + /*---- Main runner ----*/ int main(void) { + testSize(); + testGetTextProperties(); testGetNumDataCodewords(); testGetNumRawDataModules(); + testCalcReedSolomonGenerator(); + testCalcReedSolomonRemainder(); testFiniteFieldMultiply(); + testGetAlignmentPatternPositions(); printf("All %d test cases passed\n", numTestCases); return EXIT_SUCCESS; } diff --git a/c/qrcodegen.c b/c/qrcodegen.c index 0586202..79206a0 100644 --- a/c/qrcodegen.c +++ b/c/qrcodegen.c @@ -43,7 +43,7 @@ // Also, each of these functions allocate only a small constant amount of memory on the stack, // they don't allocate or free anything on the heap, and they are thread-safe. -static int getTextProperties(const char *text, bool *isNumeric, bool *isAlphanumeric, int *textBits); +testable int getTextProperties(const char *text, bool *isNumeric, bool *isAlphanumeric, int *textBits); static int fitVersionToData(int minVersion, int maxVersion, enum qrcodegen_Ecc ecl, int dataLen, int dataBitLen, int ver1To9LenBits, int ver10To26LenBits, int ver27To40LenBits); static void encodeQrCodeTail(uint8_t dataAndQrcode[], int bitLen, uint8_t tempBuffer[], int version, enum qrcodegen_Ecc ecl, enum qrcodegen_Mask mask, bool boostEcl); static void appendBitsToBuffer(unsigned int val, int numBits, uint8_t buffer[], int *bitLen); @@ -52,14 +52,14 @@ static void appendErrorCorrection(uint8_t data[], int version, enum qrcodegen_Ec testable int getNumDataCodewords(int version, enum qrcodegen_Ecc ecl); testable int getNumRawDataModules(int version); -static void calcReedSolomonGenerator(int degree, uint8_t result[]); -static void calcReedSolomonRemainder(const uint8_t data[], int dataLen, const uint8_t generator[], int degree, uint8_t result[]); +testable void calcReedSolomonGenerator(int degree, uint8_t result[]); +testable void calcReedSolomonRemainder(const uint8_t data[], int dataLen, const uint8_t generator[], int degree, uint8_t result[]); testable uint8_t finiteFieldMultiply(uint8_t x, uint8_t y); static void initializeFunctionModules(int version, uint8_t qrcode[]); static void drawWhiteFunctionModules(uint8_t qrcode[], int version); static void drawFormatBits(enum qrcodegen_Ecc ecl, enum qrcodegen_Mask mask, uint8_t qrcode[], int qrsize); -static int getAlignmentPatternPositions(int version, uint8_t result[7]); +testable int getAlignmentPatternPositions(int version, uint8_t result[7]); static void fillRectangle(int left, int top, int width, int height, uint8_t qrcode[], int qrsize); static void drawCodewords(const uint8_t data[], int dataLen, uint8_t qrcode[], int qrsize); @@ -207,7 +207,7 @@ int qrcodegen_encodeBinary(uint8_t dataAndTemp[], size_t dataLen, uint8_t qrcode // Returns a negative number if the length would exceed INT16_MAX or textBits would exceed INT_MAX. // Note that INT16_MAX <= 32767 <= INT_MAX and INT16_MAX < 65535 <= SIZE_MAX. // If the return value is negative, then the pointees of output arguments might not be set. -static int getTextProperties(const char *text, bool *isNumeric, bool *isAlphanumeric, int *textBits) { +testable int getTextProperties(const char *text, bool *isNumeric, bool *isAlphanumeric, int *textBits) { int textLen = 0; *isNumeric = true; *isAlphanumeric = true; @@ -401,7 +401,7 @@ testable int getNumRawDataModules(int version) { /*---- Reed-Solomon ECC generator functions ----*/ // Calculates the Reed-Solomon generator polynomial of the given degree, storing in result[0 : degree]. -static void calcReedSolomonGenerator(int degree, uint8_t result[]) { +testable void calcReedSolomonGenerator(int degree, uint8_t result[]) { // Start with the monomial x^0 assert(1 <= degree && degree <= 30); memset(result, 0, degree * sizeof(result[0])); @@ -425,7 +425,7 @@ static void calcReedSolomonGenerator(int degree, uint8_t result[]) { // Calculates the remainder of the polynomial data[0 : dataLen] when divided by the generator[0 : degree], where all // polynomials are in big endian and the generator has an implicit leading 1 term, storing the result in result[0 : degree]. -static void calcReedSolomonRemainder(const uint8_t data[], int dataLen, const uint8_t generator[], int degree, uint8_t result[]) { +testable void calcReedSolomonRemainder(const uint8_t data[], int dataLen, const uint8_t generator[], int degree, uint8_t result[]) { // Perform polynomial division assert(1 <= degree && degree <= 30); memset(result, 0, degree * sizeof(result[0])); @@ -595,7 +595,7 @@ static void drawFormatBits(enum qrcodegen_Ecc ecl, enum qrcodegen_Mask mask, uin // Calculates the positions of alignment patterns in ascending order for the given version number, // storing them to the given array and returning an array length in the range [0, 7]. -static int getAlignmentPatternPositions(int version, uint8_t result[7]) { +testable int getAlignmentPatternPositions(int version, uint8_t result[7]) { if (version == 1) return 0; int qrsize = qrcodegen_getSize(version);