1 local getopt
= require('getopt')
2 local lib14a
= require('read14a')
3 local cmds
= require('commands')
4 local ansicolors
= require('ansicolors')
6 copyright
= 'Copyright 2020 A. Ozkal, released under GPLv2+.'
10 This script writes a bunch of random blocks to a NTAG or MFUL card to test its actual write limits
13 script run ntag_hammertime -w 1000 -r 50 -z 50 -f 5 -s 4 -e 129
16 script run ntag_hammertime [-h] [-w <writecount>] [-r <readevery>] [-z <reselectevery>] [-f <maximumfails>] [-s <writestartblock>] [-e <writeendblock>]
20 -w <writeroundcount> : Amount of write rounds to be done to each block (optional, default: 100)
21 -r <readevery> : Verify frequency (reads and checks written values every x rounds, optional, default: 10)
22 -z <reselectevery> : Reselect frequency (reselects card once every x rounds, optional, default: 10)
23 -f <maximumfails> : Maximum consequent fails (read/write) that will trigger a fail state (optional, default: 3)
24 -s <writestartblock> : Block number for writes to be started to (optional, inclusive, decimal, default: 4)
25 -e <writeendblock> : Block number for writes to be ended on (optional, inclusive, decimal, default: 129)
32 print(ansicolors
.cyan
..'Usage'..ansicolors
.reset
)
34 print(ansicolors
.cyan
..'Arguments'..ansicolors
.reset
)
36 print(ansicolors
.cyan
..'Example usage'..ansicolors
.reset
)
44 -- 48-57 numbers, 65-70 a-f
45 hex
= math
.random(0, 15)
49 result
= result
..string.char(48 + hex
)
54 -- Used to send raw data to the firmware to subsequently forward the data to the card.
55 -- from mifareplus.lua
56 local function sendRaw(rawdata
, crc
, power
)
57 -- print(("<sent>: %s"):format(rawdata))
59 local flags
= lib14a
.ISO14A_COMMAND
.ISO14A_RAW
61 flags
= flags
+ lib14a
.ISO14A_COMMAND
.ISO14A_APPEND_CRC
64 flags
= flags
+ lib14a
.ISO14A_COMMAND
.ISO14A_NO_DISCONNECT
67 local command
= Command
:newMIX
{cmd
= cmds
.CMD_HF_ISO14443A_READER
,
68 arg1
= flags
, -- Send raw
69 arg2
= string.len(rawdata
) / 2, -- arg2 contains the length, which is half the length of the ASCII-string rawdata
72 local ignore_response
= false
73 local result
, err
= command
:sendMIX(ignore_response
)
75 --unpack the first 4 parts of the result as longs, and the last as an extremely long string to later be cut down based on arg1, the number of bytes returned
76 local count
,cmd
,arg1
,arg2
,arg3
,data
= bin
.unpack('LLLLH512',result
)
78 returned_bytes
= string.sub(data
, 1, arg1
* 2)
79 if #returned_bytes
> 0 then
80 -- print(("<recvd>: %s"):format(returned_bytes)) -- need to multiply by 2 because the hex digits are actually two bytes when they are strings
86 print("Error sending the card raw data.")
91 local function selectCard(keepField
, arg2
, attemptCount
)
92 for i
= 1,attemptCount
,1
94 if lib14a
.read(keepField
, arg2
) then
102 -- The main entry point
114 -- Read the parameters
115 for o
, a
in getopt
.getopt(args
, 'hw:r:z:f:s:e:') do
116 if o
== 'h' then return help() end
117 if o
== 'w' then loopcount
= tonumber(a
) end
118 if o
== 'r' then verifyevery
= tonumber(a
) end
119 if o
== 'z' then reselectevery
= tonumber(a
) end
120 if o
== 'f' then failmax
= tonumber(a
) end
121 if o
== 's' then blockstart
= tonumber(a
) end
122 if o
== 'e' then blockend
= tonumber(a
) end
125 starttime
= os
.time()
127 if selectCard(true, false, 3) ~= true then
128 return print("Select failed.")
130 for i
= 1,loopcount
,1
132 for block
= blockstart
,blockend
,1
135 print(i
..": Writing "..data
.." to block "..block
..".")
136 blockhex
= string.format("%02x", block
)
137 result
= sendRaw("A2"..blockhex
..data
, true, true)
138 if result
then -- if false/nil, that's a fail right there
139 print(ansicolors
.green
.."Got "..result
.."."..ansicolors
.reset
) -- We want this to be 0A
142 print(ansicolors
.red
.."Write FAILED."..ansicolors
.reset
)
143 failcounter
= failcounter
+ 1
147 if i
% verifyevery
== 0 then
148 result
= sendRaw("30"..blockhex
, true, true)
149 if result
then -- if false, that's a fail right there
150 result
= string.sub(result
, 0, 8)
151 if result
~= data
then
152 print(ansicolors
.red
.."Read IMPROPER, supposed to be "..data
..", got "..result
.."."..ansicolors
.reset
)
153 failcounter
= failcounter
+ 1
156 print(ansicolors
.green
.."Read matches the write."..ansicolors
.reset
)
160 print(ansicolors
.red
.."Read FAILED."..ansicolors
.reset
)
161 failcounter
= failcounter
+ 1
167 if failcounter
>= failmax
then
169 lib14a
.read(false, false)
170 return print(ansicolors
.red
.."Test failed after "..(os
.time() - starttime
).." seconds, "..(i
*(blockend
-blockstart
)).." writes and "..math
.floor((i
*(blockend
-blockstart
))/verifyevery
).." reads."..ansicolors
.reset
)
174 if i
% reselectevery
== 0 then
176 sendRaw("", false, false)
177 if selectCard(true, false, 3) ~= true then
178 return print("Reselect failed.")
180 print("Reselected card, current rate: "..(i
*(blockend
-blockstart
))/(os
.time() - starttime
).." writes/s.")
185 lib14a
.read(false, false)
186 print("Successfully completed test in "..(os
.time() - starttime
).." seconds, did "..(loopcount
*(blockend
-blockstart
)).." writes and "..math
.floor((loopcount
*(blockend
-blockstart
))/verifyevery
).." reads.")