diff --git a/libraries/LUHN/CHANGELOG.md b/libraries/LUHN/CHANGELOG.md index f12fbb2e..cdd6a2cc 100644 --- a/libraries/LUHN/CHANGELOG.md +++ b/libraries/LUHN/CHANGELOG.md @@ -6,6 +6,19 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## [0.2.0] - 2023-05-08 +- add unit test +- fix stream mode - **add()** and **reset()** +- fix bugs in generation / validation. +- add **generateChecksum(const char \* buffer)** +- change **count** to be 32 bit to support really large streams. +- add **count()** function to return internal counter. +- updates readme.md +- 0.1.x versions are obsolete + + +---- + ## [0.1.1] - 2022-12-29 - add stream interface - **char add(char c)** @@ -14,6 +27,5 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - update documentation - fix warning - ## [0.1.0] - 2022-12-24 - initial version diff --git a/libraries/LUHN/LUHN.cpp b/libraries/LUHN/LUHN.cpp index 373a5cf5..9afb2002 100644 --- a/libraries/LUHN/LUHN.cpp +++ b/libraries/LUHN/LUHN.cpp @@ -1,7 +1,7 @@ // // FILE: LUHN.cpp // AUTHOR: Rob Tillaart -// VERSION: 0.1.1 +// VERSION: 0.2.0 // DATE: 2022-12-24 // PURPOSE: Arduino Library for calculating LUHN checksum. // URL: https://github.com/RobTillaart/LUHN @@ -12,8 +12,9 @@ LUHN::LUHN() { - _luhn = 0; - _count = 0; + _luhnEven = 0; + _luhnOdd = 0; + _count = 0; } @@ -29,10 +30,11 @@ bool LUHN::isValid(char * buffer) uint8_t length = strlen(buffer); if (length == 0) return false; + uint8_t parity = length & 1; for (int i = 0; i < length-1; i++) { uint8_t x = buffer[i] - '0'; - if (i % 2 == 0) checksum += x; // weight 1 + if (i % 2 != parity) checksum += x; // weight 1 else if (x < 5) checksum += x * 2; // weight 2 else checksum += (x * 2 - 10 + 1); // weight 2 + handle overflow. } @@ -42,14 +44,21 @@ bool LUHN::isValid(char * buffer) } +char LUHN::generateChecksum(const char * buffer) +{ + return generateChecksum((char *) buffer); +} + + char LUHN::generateChecksum(char * buffer) { uint16_t checksum = 0; uint8_t length = strlen(buffer); + uint8_t parity = length & 1; for (int i = 0; i < length; i++) { uint8_t x = buffer[i] - '0'; - if (i % 2 == 0) checksum += x; // weight 1 + if (i % 2 == parity) checksum += x; // weight 1 else if (x < 5) checksum += x * 2; // weight 2 else checksum += (x * 2 - 10 + 1); // weight 2 + handle overflow. } @@ -83,25 +92,44 @@ bool LUHN::generate(char * buffer, uint8_t length, char * prefix) } +////////////////////////////////////////////////////////////// +// +// STREAM INTERFACE +// char LUHN::add(char c) { + // as we do not know the final length in advance + // both parity's must be calculated. uint8_t x = c - '0'; - if (_count % 2 == 0) _luhn += x; - else if (x < 5) _luhn += x * 2; - else _luhn += (x * 2 - 10 + 1); - // correct - if (_luhn > 9) _luhn -= 10; + // handle even lengths + if (_count % 2 == 0) _luhnEven += x; + else if (x < 5) _luhnEven += x * 2; + else _luhnEven += (x * 2 - 10 + 1); + // handle odd lengths + if (_count % 2 == 1) _luhnOdd += x; + else if (x < 5) _luhnOdd += x * 2; + else _luhnOdd += (x * 2 - 10 + 1); + _count++; - return '0' + (10 - _luhn); + if (_count & 1) return '0' + (100 - _luhnOdd) % 10; + return '0' + (100 - _luhnEven) % 10; } char LUHN::reset() { - uint8_t last = _luhn; - _luhn = 0; - _count = 0; - return '0' + (10 - last); + uint8_t last = _luhnEven; + if (_count & 1) last = _luhnOdd; + _luhnEven = 0; + _luhnOdd = 0; + _count = 0; + return '0' + (100 - last) % 10; +} + + +uint32_t LUHN::count() +{ + return _count; } @@ -125,5 +153,25 @@ uint8_t LUHN::Marsaglia_mod10() } +// EXPERIMENTAL +// ~8% faster and 58 bytes bigger (UNO) +// uint8_t LUHN::Marsaglia_mod10() +// { + // static uint32_t value; + // static uint8_t digits = 0; + // if (digits == 0) + // { + // digits = 4; + // m_z = 36969L * (m_z & 65535L) + (m_z >> 16); + // m_w = 18000L * (m_w & 65535L) + (m_w >> 16); + // value = (m_z ^ m_w); + // } + // uint8_t rv = value % 10; + // value >>= 8; + // digits--; + // return rv; +// } + + // -- END OF FILE -- diff --git a/libraries/LUHN/LUHN.h b/libraries/LUHN/LUHN.h index d96a965d..f81be8f6 100644 --- a/libraries/LUHN/LUHN.h +++ b/libraries/LUHN/LUHN.h @@ -2,7 +2,7 @@ // // FILE: LUHN.h // AUTHOR: Rob Tillaart -// VERSION: 0.1.1 +// VERSION: 0.2.0 // DATE: 2022-12-24 // PURPOSE: Arduino Library for calculating LUHN checksum. // URL: https://github.com/RobTillaart/LUHN @@ -10,7 +10,7 @@ #include "Arduino.h" -#define LUHN_LIB_VERSION (F("0.1.1")) +#define LUHN_LIB_VERSION (F("0.2.0")) class LUHN @@ -22,6 +22,7 @@ public: // buffer == \0 terminated bool isValid(const char * buffer); bool isValid(char * buffer); + char generateChecksum(const char * buffer); char generateChecksum(char * buffer); // GENERATE A PRODUCT ID WITH LUHN CHECKSUM @@ -31,15 +32,16 @@ public: // STREAM INTERFACE char add(char c); char reset(); - + uint32_t count(); protected: uint32_t m_w = 1; // random generator parameter uint32_t m_z = 2; // random generator parameter uint8_t Marsaglia_mod10(); - - uint8_t _luhn = 0; - uint16_t _count = 0; + + uint16_t _luhnEven = 0; + uint16_t _luhnOdd = 0; + uint32_t _count = 0; }; diff --git a/libraries/LUHN/README.md b/libraries/LUHN/README.md index 4879aeca..7a552180 100644 --- a/libraries/LUHN/README.md +++ b/libraries/LUHN/README.md @@ -20,13 +20,28 @@ The LUHN check uses very few resources and is pretty fast. Basic idea is to put all digits-1 through the formula and the output should equal the last digit. -Note: some LUHN validations uses the reversed product string. +This LUHN library also includes a "stream" based LUHN calculation, in which digits can be add +one at a time (from a stream) and it will return the LUHN checksum so far. +This is a new application as LUHN depends on the length of the input being odd or even. +To handle this two values are maintained (for odd and even lengths) and the correct one is returned. + +Maintaining two checksums makes the stream **add(c)** algorithm substantial slower (~4x) than +the normally used **generateChecksum(buffer)** or the **isValid(buffer)**. +However that is a small price for the new functionality. + +The amount of data that can be added in stream mode is infinite in theory. +However that is not tested for obvious reasons, internally a 32 bit counter exists. + + +#### Notes + +- some LUHN validations uses the reversed product string. +- 0.1.x versions are obsolete due to incorrect math. + + +#### Links - https://en.wikipedia.org/wiki/Luhn_algorithm - - -#### related - - https://github.com/RobTillaart/Adler - https://github.com/RobTillaart/CRC - https://github.com/RobTillaart/Fletcher @@ -46,6 +61,7 @@ The parameter buffer is a '\0' terminated char array. Length should be less than - **char generateChecksum(char \* buffer)** Returns the char '0'..'9' which is the checksum of the code in the parameter buffer. The parameter buffer is a '\0' terminated char array. Length should be less than 254. +- **char generateChecksum(const char \* buffer)** idem. - **bool generate(char \* buffer, uint8_t length, char \* prefix)** Generates a char array including LUHN number with a defined prefix of max length. Returns false if the prefix exceeds length -1. @@ -53,26 +69,37 @@ Returns false if the prefix exceeds length -1. #### Stream -- **char add(char c)** add char, return LUHN so far. +- **char add(char c)** add char, returns LUHN so far. - **char reset()** return last LUHN. +- **uint32_t count()** return internal counter. +If this value is zero, a new LUHN can be calculated, otherwise call **reset()** first. + +The internal counter for the stream interface is 32 bit. +This limits the number of add() calls to about 4 billion. +For current implementation the counter is used for even/odd detection, +and even when it overflows one gets the correct **LUHN**. -The internal counter for the stream interface is 16 bit. -This limits the nr of add() calls to about 65530. -(if this is a problem, make it an uinit32_t) ## Future -#### must +#### Must -- update documentation -#### should +#### Should -- unit tests -- look for optimization +- look for optimizations -#### could +#### Could + +#### Won't (unless) + +- create a HEX equivalent of LUHN + - LUHN16 ? + - easy to enter HEX code with verify / line. + - mod N configurable so not only 10 but any N? +- uint64_t interface for up to 17 digits. + - expensive on small processors. - uint32_t interface for up to 8 digit ID's (99.999.999) - **isValid(uint32_t)** - **generateChecksum(uint32_t)** diff --git a/libraries/LUHN/examples/luhn_check/luhn_check.ino b/libraries/LUHN/examples/luhn_check/luhn_check.ino index 9c0859f2..ae147c5c 100644 --- a/libraries/LUHN/examples/luhn_check/luhn_check.ino +++ b/libraries/LUHN/examples/luhn_check/luhn_check.ino @@ -23,6 +23,8 @@ void setup() while (!Serial); Serial.println(); Serial.println(__FILE__); + Serial.print("LUHN_LIB_VERSION: "); + Serial.println(LUHN_LIB_VERSION); // SHOULD PRINT 3 Serial.println(checker.generateChecksum((char *)"7992739871")); diff --git a/libraries/LUHN/examples/luhn_check_stream/luhn_check_stream.ino b/libraries/LUHN/examples/luhn_check_stream/luhn_check_stream.ino index 9ea1c087..38cc072e 100644 --- a/libraries/LUHN/examples/luhn_check_stream/luhn_check_stream.ino +++ b/libraries/LUHN/examples/luhn_check_stream/luhn_check_stream.ino @@ -24,8 +24,10 @@ void setup() while (!Serial); Serial.println(); Serial.println(__FILE__); + Serial.print("LUHN_LIB_VERSION: "); + Serial.println(LUHN_LIB_VERSION); - Serial.println("run I: debug"); + Serial.println("run I: add() per character"); for (uint8_t i = 0; i < strlen(ID) - 1; i++) { c = checker.add(ID[i]); @@ -46,12 +48,45 @@ void setup() } c = checker.reset(); stop = micros(); - Serial.println(); - Serial.print("run II:\t"); - Serial.println(c); // should also print 3 . - Serial.print(" time:\t"); + Serial.print("LUHN:\t"); + Serial.println(c); // should print 3. + Serial.print("time:\t"); Serial.print(stop - start); Serial.println(); + Serial.println(); + delay(10); + + Serial.println("run II: should have same 2nd column as run 1."); + for (uint8_t i = 0; i < strlen(ID); i++) + { + char tmp[32]; + strcpy(tmp, ID); + tmp[i] = 0; + c = checker.generateChecksum(tmp); + Serial.print(tmp); + Serial.print("\t"); + Serial.println(c); + } + Serial.println(); + delay(10); + + + char tmp[32]; + strcpy(tmp, ID); + tmp[strlen(ID)-1] = 0; + start = micros(); + c = checker.generateChecksum(tmp); + stop = micros(); + Serial.println(); + Serial.println("run III: generateChecksum()"); + Serial.print("LUHN:\t"); + Serial.println(c); // should print 3. + Serial.print("time:\t"); + Serial.print(stop - start); + Serial.println(); + Serial.println(); + delay(10); + Serial.println("\ndone..."); } diff --git a/libraries/LUHN/examples/luhn_check_stream/output_0.2.0.txt b/libraries/LUHN/examples/luhn_check_stream/output_0.2.0.txt new file mode 100644 index 00000000..4ed03e6c --- /dev/null +++ b/libraries/LUHN/examples/luhn_check_stream/output_0.2.0.txt @@ -0,0 +1,40 @@ +Arduino UNO +IDE 1.8.19 + +luhn_check_stream.ino +LUHN_LIB_VERSION: 0.2.0 +run I: add() per character +7 5 +9 4 +9 7 +2 1 +7 0 +3 8 +9 8 +8 2 +7 5 +1 3 + +LUHN: 3 +time: 256 + +run II: should have same 2nd column as run 1. + 0 +7 5 +79 4 +799 7 +7992 1 +79927 0 +799273 8 +7992739 8 +79927398 2 +799273987 5 +7992739871 3 + + +run III: generateChecksum() +LUHN: 3 +time: 64 + + +done... diff --git a/libraries/LUHN/examples/luhn_generate/luhn_generate.ino b/libraries/LUHN/examples/luhn_generate/luhn_generate.ino index 347180cb..3a713654 100644 --- a/libraries/LUHN/examples/luhn_generate/luhn_generate.ino +++ b/libraries/LUHN/examples/luhn_generate/luhn_generate.ino @@ -22,6 +22,10 @@ void setup() while (!Serial); Serial.println(); Serial.println(__FILE__); + Serial.print("LUHN_LIB_VERSION: "); + Serial.println(LUHN_LIB_VERSION); + Serial.println(); + delay(10); } @@ -35,7 +39,8 @@ void loop() Serial.print(checker.isValid(number)); Serial.print("\t"); Serial.print(stop - start); - // Serial.print((1.0*(stop - start))/ strlen(number)); per digit + Serial.print("\t"); + Serial.print((1.0 * (stop - start)) / strlen(number)); // per digit Serial.print("\t"); Serial.println(number); delay(100); diff --git a/libraries/LUHN/keywords.txt b/libraries/LUHN/keywords.txt index 43b3ccea..4eb757f1 100644 --- a/libraries/LUHN/keywords.txt +++ b/libraries/LUHN/keywords.txt @@ -11,6 +11,8 @@ generateChecksum KEYWORD2 randomize KEYWORD2 generate KEYWORD2 +add KEYWORD2 +reset KEYWORD2 # Constants (LITERAL1) LUHN_LIB_VERSION LITERAL1 diff --git a/libraries/LUHN/library.json b/libraries/LUHN/library.json index 44113262..7a348ef1 100644 --- a/libraries/LUHN/library.json +++ b/libraries/LUHN/library.json @@ -15,7 +15,7 @@ "type": "git", "url": "https://github.com/RobTillaart/LUHN.git" }, - "version": "0.1.1", + "version": "0.2.0", "license": "MIT", "frameworks": "arduino", "platforms": "*", diff --git a/libraries/LUHN/library.properties b/libraries/LUHN/library.properties index e60f5acb..09c42b66 100644 --- a/libraries/LUHN/library.properties +++ b/libraries/LUHN/library.properties @@ -1,5 +1,5 @@ name=LUHN -version=0.1.1 +version=0.2.0 author=Rob Tillaart maintainer=Rob Tillaart sentence=Arduino Library for calculating LUHN checksum. diff --git a/libraries/LUHN/test/unit_test_001.cpp b/libraries/LUHN/test/unit_test_001.cpp new file mode 100644 index 00000000..c4b19193 --- /dev/null +++ b/libraries/LUHN/test/unit_test_001.cpp @@ -0,0 +1,131 @@ +// +// FILE: unit_test_001.cpp +// AUTHOR: Rob Tillaart +// DATE: 2023-05-02 +// PURPOSE: unit tests for the LUHN checksum +// https://github.com/RobTillaart/LUHN +// https://github.com/Arduino-CI/arduino_ci/blob/master/REFERENCE.md +// + +// supported assertions +// ---------------------------- +// assertEqual(expected, actual) +// assertNotEqual(expected, actual) +// assertLess(expected, actual) +// assertMore(expected, actual) +// assertLessOrEqual(expected, actual) +// assertMoreOrEqual(expected, actual) +// assertTrue(actual) +// assertFalse(actual) +// assertNull(actual) + + +#include + +#include "Arduino.h" +#include "LUHN.h" + + +unittest_setup() +{ + fprintf(stderr, "LUHN_LIB_VERSION: %s\n", (char *) LUHN_LIB_VERSION); +} + +unittest_teardown() +{ +} + + +unittest(test_constructor) +{ + LUHN luhn; + + assertEqual('0', luhn.reset()); + assertEqual('0', luhn.reset()); +} + + +unittest(test_isValid) +{ + LUHN luhn; + + assertTrue(luhn.isValid("0")); + assertTrue(luhn.isValid("79927398713")); + assertTrue(luhn.isValid("1111111")); + assertTrue(luhn.isValid("11111111111111111111")); + + // generated by https://www.dcode.fr/luhn-algorithm + assertTrue(luhn.isValid("08192186963920766401")); + + + // https://www.mobilefish.com/services/credit_card_number_generator/credit_card_number_generator.php + + fprintf(stderr, "Example American Express credit card number\n"); + // An American Express credit card number starts with number 34 or 37 and + // the credit card number has total 15 digits: + assertTrue(luhn.isValid("377261620324999")); + assertTrue(luhn.isValid("349854194206314")); + + fprintf(stderr, "Example Mastercard credit card number\n"); + // A Mastercard credit card number starts with number 51, 52, 53, 54 or 55 and + // the credit card number has total 16 digits: + assertTrue(luhn.isValid("5181975718047403")); + assertTrue(luhn.isValid("5204571199083364")); + assertTrue(luhn.isValid("5322683667269933")); + assertTrue(luhn.isValid("5477754834149242")); + assertTrue(luhn.isValid("5539624233693270")); + + fprintf(stderr, "Example Laser credit card number\n"); + // A Laser credit card number starts with number 6304, 6706, 6771 or 6709 and + // the credit card number has total 16, 17, 18 or 19 digits: + assertTrue(luhn.isValid("670676038979126821")); + assertTrue(luhn.isValid("6771363087405086")); + assertTrue(luhn.isValid("6304096514549839")); + assertTrue(luhn.isValid("6304219447607087665")); +} + + +unittest(test_generateChecksum) +{ + LUHN luhn; + + assertEqual('3', luhn.generateChecksum("7992739871")); +} + + +unittest(test_generate) +{ + LUHN luhn; + + for (int i = 0; i < 10; i++) + { + char buffer[24]; + char prefix[10] = "007"; + assertTrue(luhn.generate(buffer, 20, prefix)); + assertTrue(luhn.isValid(buffer)); + fprintf(stderr, "%d %s\n", i, buffer); + } +} + + +unittest(test_stream) +{ + LUHN luhn; + + luhn.reset(); + char buffer[24] = "7992739871"; + for (int i = 0; i < strlen(buffer); i++) + { + char c = luhn.add(buffer[i]); + fprintf(stderr, "%d %c\n", i, c); + } + assertEqual('3', luhn.reset()); +} + + + + +unittest_main() + + +// -- END OF FILE --