2 -- lf_bulk_program.lua - A tool to clone a large number of tags at once.
 
   5 -- The getopt-functionality is loaded from pm3/client/lualibs/getopt.lua
 
   6 -- Have a look there for further details
 
   7 getopt = require('getopt')
 
   8 bit32 = require('bit32')
 
  10 usage = [[ script run lf_bulk_program.lua -f facility -b base_id_num -c count
 
  13    script run lf_bulk_program.lua -f 1 -b 1000 -c 10
 
  15 author = "Brian Redbeard"
 
  17 Perform bulk enrollment of 26 bit H10301 style RFID Tags
 
  18 For more info, check the comments in the code
 
  21 --[[Implement a function to simply visualize the bitstream in a text format
 
  22 --This is especially helpful for troubleshooting bitwise math issues]]--
 
  23 function toBits(num,bits)
 
  24     -- returns a table of bits, most significant first.
 
  25     bits = bits or math.max(1, select(2, math.frexp(num)))
 
  26     local t = {} -- will contain the bits
 
  27     for b = bits, 1, -1 do
 
  28         t[b] = math.fmod(num, 2)
 
  29         num = math.floor((num - t[b]) / 2)
 
  31     return table.concat(t)
 
  34 --[[Likely, I'm an idiot, but I couldn't find any parity functions in Lua
 
  35   This can also be done with a combination of bitwise operations (in fact, 
 
  36   is the canonically "correct" way to do it, but my brain doesn't just
 
  37   default to this and so counting some ones is good enough for me]]--
 
  38 local function evenparity(s)
 
  39         local _, count = string.gsub(s, "1", "")
 
  50 local function isempty(s)
 
  51         return s == nil or s == ''
 
  54 --[[The Proxmark3 "clone" functions expect the data to be in hex format so
 
  55   take the card id number and facility ID as arguments and construct the
 
  56   hex.  This should be easy enough to extend to non 26bit formats]]--
 
  57 local function cardHex(i,f)
 
  58         fac = bit32.lshift(f,16)
 
  59         id = bit32.bor(i, fac)
 
  62         --As the function defaults to even parity and returns a boolean,
 
  63         --perform a 'not' function to get odd parity
 
  64         high = evenparity(string.sub(stream,0,12)) and 1 or 0
 
  65         low = not evenparity(string.sub(stream,13)) and 1 or 0
 
  66         bits = bit32.bor(bit32.lshift(id,1), low)
 
  67         bits = bit32.bor(bits, bit32.lshift(high,25))
 
  69         --Since the lua library bit32 is (obviously) 32 bits and we need to
 
  70         --encode 36 bits to properly do a 26 bit tag with the preamble we need
 
  71         --to create a higher order and lower order component which we will
 
  72         --then assemble in the return.  The math above defines the proper
 
  73         --encoding as per HID/Weigand/etc.  These bit flips are due to the
 
  74         --format length check on bit 38 (cmdlfhid.c:64) and 
 
  75         --bit 31 (cmdlfhid.c:66).
 
  76         preamble = bit32.bor(0, bit32.lshift(1,5))
 
  77         bits = bit32.bor(bits, bit32.lshift(1,26))
 
  79         return ("%04x%08x"):format(preamble,bits)
 
  83 local function main(args)
 
  85         --I really wish a better getopt function would be brought in supporting
 
  86         --long arguments, but it seems this library was chosen for BSD style
 
  88         for o, a in getopt.getopt(args, 'f:b:c:h') do
 
  91                                 print("You did not supply a facility code, using 0")
 
  98                                 print("You must supply the flag -b (base id)")
 
 105                                 print("You must supply the flag -c (count)")
 
 117         --Due to my earlier complaints about how this specific getopt library
 
 118         --works, specifying ":" does not enforce supplying a value, thus we
 
 119         --need to do these checks all over again.
 
 121         if isempty(baseid) then 
 
 122                 print("You must supply the flag -b (base id)")
 
 127         if isempty(count) then 
 
 128                 print("You must supply the flag -c (count)")
 
 133         --If the facility ID is non specified, ensure we code it as zero
 
 134         if isempty(facility) then 
 
 135                 print("Using 0 for the facility code as -f was not supplied")
 
 139         --The next baseid + count function presents a logic/UX conflict
 
 140         --where users specifying -c 1 (count = 1) would try to program two
 
 141         --tags.  This makes it so that -c 0 & -c 1 both code one tag, and all
 
 142         --other values encode the expected amount.
 
 143         if tonumber(count) > 0 then count = count -1 end
 
 145         endid = baseid + count
 
 147         for cardnum = baseid,endid do 
 
 148                 local card = cardHex(cardnum, facility)
 
 149                 print("Press enter to program card "..cardnum..":"..facility.." (hex: "..card..")")
 
 150                 --This would be better with "press any key", but we'll take
 
 153                 core.console( ('lf hid clone %s'):format(card) )