mirror of
https://github.com/sigmasternchen/mastercard-regex
synced 2025-03-14 23:48:56 +00:00
140 lines
3.1 KiB
Python
140 lines
3.1 KiB
Python
![]() |
#!/usr/bin/env python3
|
||
|
|
||
|
from greenery import fsm, lego
|
||
|
|
||
|
# alphabet list
|
||
|
# also used as lookup table for number-strings
|
||
|
n = [str(x) for x in range(10)]
|
||
|
|
||
|
# start and end states
|
||
|
s, e = "S", "E"
|
||
|
|
||
|
# get the state name for a position and the current mod-sum
|
||
|
def p(p, n):
|
||
|
if p == 0:
|
||
|
return s
|
||
|
return str(p) + ":" + str(n)
|
||
|
|
||
|
# list of all states
|
||
|
states = [p(x, y) for x in range(1, 16) for y in range(10)] + [s, e]
|
||
|
|
||
|
# this function handles the odd number case for Luhn's algorithm
|
||
|
def modi(p, n):
|
||
|
if p % 2 == 0:
|
||
|
if n > 4:
|
||
|
return n * 2 - 9
|
||
|
else:
|
||
|
return n * 2
|
||
|
else:
|
||
|
return n
|
||
|
|
||
|
# generate all state transitions to non-final states
|
||
|
# t is the transition dictionary
|
||
|
# the keys are the current states
|
||
|
# the values are dictionaries from the input alphabet
|
||
|
# value to the next state
|
||
|
t = {}
|
||
|
for _p in range(15):
|
||
|
for n1 in range(10):
|
||
|
# _p == 0 is the initial state -> n1 is always 0
|
||
|
if _p == 0 and n1 > 0:
|
||
|
continue
|
||
|
|
||
|
t[p(_p, n1)] = {}
|
||
|
for n2 in range(10):
|
||
|
# checks for mastercard 50s range
|
||
|
if _p == 0 and n2 != 5:
|
||
|
continue
|
||
|
if _p == 1 and (n2 == 0 or n2 > 5):
|
||
|
continue
|
||
|
|
||
|
t[p(_p, n1)][n[n2]] = p(_p + 1, (n1 + modi(_p, n2)) % 10)
|
||
|
|
||
|
# generate transitions to the final state
|
||
|
for n1 in range(10):
|
||
|
t[p(15, n1)] = {}
|
||
|
for n2 in range(10):
|
||
|
if (n1 + modi(15, n2)) % 10 == 0:
|
||
|
t[p(15, n1)][n[n2]] = e
|
||
|
|
||
|
# build FSM from alphabet, state set and state transitions
|
||
|
machine = fsm.fsm(
|
||
|
alphabet = set(n),
|
||
|
states = set(states),
|
||
|
initial = s,
|
||
|
finals = {e},
|
||
|
map = t,
|
||
|
)
|
||
|
|
||
|
# hashtag #datadriven tests
|
||
|
tests = [
|
||
|
# generated valid (https://fake-name-generator.com/Fake-MasterCard-Number-Generator)
|
||
|
("5222084305213022", True),
|
||
|
("5223625675778488", True),
|
||
|
("5306937024627310", True),
|
||
|
("5406251442761555", True),
|
||
|
("5216055885117468", True),
|
||
|
("5197252600542111", True),
|
||
|
("5549661903610174", True),
|
||
|
|
||
|
("6216055885117468", False), # Luhn check fail
|
||
|
("5316055885117468", False),
|
||
|
("5226055885117468", False),
|
||
|
("5217055885117468", False),
|
||
|
("5216155885117468", False),
|
||
|
("5216065885117468", False),
|
||
|
("5216054885117468", False),
|
||
|
("5216055985117468", False),
|
||
|
("5216055875117468", False),
|
||
|
("5216055886117468", False),
|
||
|
("5216055885217468", False),
|
||
|
("5216055885107468", False),
|
||
|
("5216055885118468", False),
|
||
|
("5216055885117568", False),
|
||
|
("5216055885117478", False),
|
||
|
("5216055885117469", False),
|
||
|
("4356180747362517", False), # Visa, not Mastercard
|
||
|
]
|
||
|
|
||
|
# Luhn's check for test cases to confirm if they were generated correctly
|
||
|
# only works for mastercard numbers (the visa test will case fail here)
|
||
|
if False:
|
||
|
for n, r in tests:
|
||
|
s = 0
|
||
|
for i, c in enumerate(n):
|
||
|
s += modi(i, int(c))
|
||
|
if not (r == (s % 10 == 0)):
|
||
|
print("test case %s (%s) is invalid: checksum %d" % (n, r, s % 10))
|
||
|
exit(1)
|
||
|
|
||
|
print("Original FSM:")
|
||
|
print(machine)
|
||
|
print()
|
||
|
|
||
|
print("Reducing...")
|
||
|
machine = machine.reduce()
|
||
|
|
||
|
print("Reduced FSM:")
|
||
|
print(machine)
|
||
|
print()
|
||
|
|
||
|
print("Testing:")
|
||
|
overall = True
|
||
|
for n, r in tests:
|
||
|
print("Test case %s (%s)" % (n, r))
|
||
|
if r == machine.accepts(n):
|
||
|
print(" OK!")
|
||
|
else:
|
||
|
print(" FAIL!")
|
||
|
overall = False
|
||
|
print()
|
||
|
|
||
|
if not overall:
|
||
|
print("Test suite failed!")
|
||
|
exit(1)
|
||
|
|
||
|
print("Building regex...")
|
||
|
rex = lego.from_fsm(machine)
|
||
|
print()
|
||
|
print(rex)
|