diff --git a/c/qrcodegen-test.c b/c/qrcodegen-test.c index 590e642..3e8f8d8 100644 --- a/c/qrcodegen-test.c +++ b/c/qrcodegen-test.c @@ -43,8 +43,11 @@ static int numTestCases = 0; // Prototypes of private functions under test +extern const int8_t ECC_CODEWORDS_PER_BLOCK[4][41]; +extern const int8_t NUM_ERROR_CORRECTION_BLOCKS[4][41]; int getTextProperties(const char *text, bool *isNumeric, bool *isAlphanumeric, int *textBits); void appendBitsToBuffer(unsigned int val, int numBits, uint8_t buffer[], int *bitLen); +void appendErrorCorrection(uint8_t data[], int version, enum qrcodegen_Ecc ecl, uint8_t result[]); int getNumDataCodewords(int version, enum qrcodegen_Ecc ecl); int getNumRawDataModules(int version); void calcReedSolomonGenerator(int degree, uint8_t result[]); @@ -158,6 +161,73 @@ static void testAppendBitsToBuffer(void) { } +// Ported from the Java version of the code. +static uint8_t *appendErrorCorrectionReference(const uint8_t *data, int version, enum qrcodegen_Ecc ecl) { + // Calculate parameter numbers + int numBlocks = NUM_ERROR_CORRECTION_BLOCKS[(int)ecl][version]; + int blockEccLen = ECC_CODEWORDS_PER_BLOCK[(int)ecl][version]; + int rawCodewords = getNumRawDataModules(version) / 8; + int numShortBlocks = numBlocks - rawCodewords % numBlocks; + int shortBlockLen = rawCodewords / numBlocks; + + // Split data into blocks and append ECC to each block + uint8_t **blocks = malloc(numBlocks * sizeof(uint8_t*)); + uint8_t *generator = malloc(blockEccLen * sizeof(uint8_t)); + calcReedSolomonGenerator(blockEccLen, generator); + for (int i = 0, k = 0; i < numBlocks; i++) { + uint8_t *block = malloc((shortBlockLen + 1) * sizeof(uint8_t)); + int blockDataLen = shortBlockLen - blockEccLen + (i < numShortBlocks ? 0 : 1); + memcpy(block, &data[k], blockDataLen * sizeof(uint8_t)); + calcReedSolomonRemainder(&data[k], blockDataLen, generator, blockEccLen, &block[shortBlockLen + 1 - blockEccLen]); + k += blockDataLen; + blocks[i] = block; + } + free(generator); + + // Interleave (not concatenate) the bytes from every block into a single sequence + uint8_t *result = malloc(rawCodewords * sizeof(uint8_t)); + for (int i = 0, k = 0; i < shortBlockLen + 1; i++) { + for (int j = 0; j < numBlocks; j++) { + // Skip the padding byte in short blocks + if (i != shortBlockLen - blockEccLen || j >= numShortBlocks) { + result[k] = blocks[j][i]; + k++; + } + } + } + for (int i = 0; i < numBlocks; i++) + free(blocks[i]); + free(blocks); + return result; +} + + +static void testAppendErrorCorrection(void) { + for (int version = 1; version <= 40; version++) { + for (int ecl = 0; ecl < 4; ecl++) { + int dataLen = getNumDataCodewords(version, (enum qrcodegen_Ecc)ecl); + uint8_t *pureData = malloc(dataLen * sizeof(uint8_t)); + for (int i = 0; i < dataLen; i++) + pureData[i] = rand() % 256; + uint8_t *expectOutput = appendErrorCorrectionReference(pureData, version, (enum qrcodegen_Ecc)ecl); + + int dataAndEccLen = getNumRawDataModules(version) / 8; + uint8_t *paddedData = malloc(dataAndEccLen * sizeof(uint8_t)); + memcpy(paddedData, pureData, dataLen * sizeof(uint8_t)); + uint8_t *actualOutput = malloc(dataAndEccLen * sizeof(uint8_t)); + appendErrorCorrection(paddedData, version, (enum qrcodegen_Ecc)ecl, actualOutput); + + assert(memcmp(actualOutput, expectOutput, dataAndEccLen * sizeof(uint8_t)) == 0); + free(pureData); + free(expectOutput); + free(paddedData); + free(actualOutput); + numTestCases++; + } + } +} + + static void testGetNumDataCodewords(void) { int cases[][3] = { { 3, 1, 44}, @@ -492,6 +562,7 @@ int main(void) { srand(time(NULL)); testGetTextProperties(); testAppendBitsToBuffer(); + testAppendErrorCorrection(); testGetNumDataCodewords(); testGetNumRawDataModules(); testCalcReedSolomonGenerator(); diff --git a/c/qrcodegen.c b/c/qrcodegen.c index 8d8e115..a1466d0 100644 --- a/c/qrcodegen.c +++ b/c/qrcodegen.c @@ -60,7 +60,7 @@ static void encodeQrCodeTail(uint8_t dataAndQrcode[], int bitLen, uint8_t tempBu int version, enum qrcodegen_Ecc ecl, enum qrcodegen_Mask mask, bool boostEcl); testable void appendBitsToBuffer(unsigned int val, int numBits, uint8_t buffer[], int *bitLen); -static void appendErrorCorrection(uint8_t data[], int version, enum qrcodegen_Ecc ecl, uint8_t result[]); +testable void appendErrorCorrection(uint8_t data[], int version, enum qrcodegen_Ecc ecl, uint8_t result[]); testable int getNumDataCodewords(int version, enum qrcodegen_Ecc ecl); testable int getNumRawDataModules(int version); @@ -90,7 +90,7 @@ testable void setModuleBounded(uint8_t qrcode[], int x, int y, bool isBlack); static const char *ALPHANUMERIC_CHARSET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:"; // For generating error correction codes. -static const int8_t ECC_CODEWORDS_PER_BLOCK[4][41] = { +testable const int8_t ECC_CODEWORDS_PER_BLOCK[4][41] = { // Version: (note that index 0 is for padding, and is set to an illegal value) //0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level {-1, 7, 10, 15, 20, 26, 18, 20, 24, 30, 18, 20, 24, 26, 30, 22, 24, 28, 30, 28, 28, 28, 28, 30, 30, 26, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30}, // Low @@ -100,7 +100,7 @@ static const int8_t ECC_CODEWORDS_PER_BLOCK[4][41] = { }; // For generating error correction codes. -const int8_t NUM_ERROR_CORRECTION_BLOCKS[4][41] = { +testable const int8_t NUM_ERROR_CORRECTION_BLOCKS[4][41] = { // Version: (note that index 0 is for padding, and is set to an illegal value) //0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level {-1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 4, 4, 4, 4, 4, 6, 6, 6, 6, 7, 8, 8, 9, 9, 10, 12, 12, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 24, 25}, // Low @@ -356,7 +356,7 @@ testable void appendBitsToBuffer(unsigned int val, int numBits, uint8_t buffer[] // from the blocks and stores them in the result array. data[0 : rawCodewords - totalEcc] contains // the input data. data[rawCodewords - totalEcc : rawCodewords] is used as a temporary work area // and will be clobbered by this function. The final answer is stored in result[0 : rawCodewords]. -static void appendErrorCorrection(uint8_t data[], int version, enum qrcodegen_Ecc ecl, uint8_t result[]) { +testable void appendErrorCorrection(uint8_t data[], int version, enum qrcodegen_Ecc ecl, uint8_t result[]) { // Calculate parameter numbers assert(0 <= (int)ecl && (int)ecl < 4 && qrcodegen_VERSION_MIN <= version && version <= qrcodegen_VERSION_MAX); int numBlocks = NUM_ERROR_CORRECTION_BLOCKS[(int)ecl][version];