mirror of
https://github.com/RobTillaart/Arduino.git
synced 2024-10-03 18:09:02 -04:00
0.2.0 LUHN
This commit is contained in:
parent
56083a3b85
commit
43f55d1eaa
@ -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
|
||||
|
@ -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,7 +12,8 @@
|
||||
|
||||
LUHN::LUHN()
|
||||
{
|
||||
_luhn = 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;
|
||||
uint8_t last = _luhnEven;
|
||||
if (_count & 1) last = _luhnOdd;
|
||||
_luhnEven = 0;
|
||||
_luhnOdd = 0;
|
||||
_count = 0;
|
||||
return '0' + (10 - last);
|
||||
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 --
|
||||
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
||||
|
@ -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)**
|
||||
|
@ -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"));
|
||||
|
@ -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...");
|
||||
}
|
||||
|
40
libraries/LUHN/examples/luhn_check_stream/output_0.2.0.txt
Normal file
40
libraries/LUHN/examples/luhn_check_stream/output_0.2.0.txt
Normal file
@ -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...
|
@ -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);
|
||||
|
@ -11,6 +11,8 @@ generateChecksum KEYWORD2
|
||||
randomize KEYWORD2
|
||||
generate KEYWORD2
|
||||
|
||||
add KEYWORD2
|
||||
reset KEYWORD2
|
||||
|
||||
# Constants (LITERAL1)
|
||||
LUHN_LIB_VERSION LITERAL1
|
||||
|
@ -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": "*",
|
||||
|
@ -1,5 +1,5 @@
|
||||
name=LUHN
|
||||
version=0.1.1
|
||||
version=0.2.0
|
||||
author=Rob Tillaart <rob.tillaart@gmail.com>
|
||||
maintainer=Rob Tillaart <rob.tillaart@gmail.com>
|
||||
sentence=Arduino Library for calculating LUHN checksum.
|
||||
|
131
libraries/LUHN/test/unit_test_001.cpp
Normal file
131
libraries/LUHN/test/unit_test_001.cpp
Normal file
@ -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 <ArduinoUnitTests.h>
|
||||
|
||||
#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 --
|
Loading…
Reference in New Issue
Block a user