| 1 |
#!/usr/bin/env python
|
| 2 |
|
| 3 |
# matrix.py generate a matrix of all readers characteristics
|
| 4 |
# Copyright (C) 2009 Ludovic Rousseau
|
| 5 |
#
|
| 6 |
# This program is free software; you can redistribute it and/or modify
|
| 7 |
# it under the terms of the GNU General Public License as published by
|
| 8 |
# the Free Software Foundation; either version 2 of the License, or
|
| 9 |
# (at your option) any later version.
|
| 10 |
#
|
| 11 |
# This program is distributed in the hope that it will be useful,
|
| 12 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
| 13 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
| 14 |
# GNU General Public License for more details.
|
| 15 |
#
|
| 16 |
# You should have received a copy of the GNU General Public License along
|
| 17 |
# with this program; if not, write to the Free Software Foundation, Inc.,
|
| 18 |
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
| 19 |
|
| 20 |
import glob
|
| 21 |
import os
|
| 22 |
import ConfigParser
|
| 23 |
import pprint
|
| 24 |
import templayer
|
| 25 |
import time
|
| 26 |
|
| 27 |
pp = pprint.PrettyPrinter(indent=4)
|
| 28 |
|
| 29 |
def parse_reader(path, reader):
|
| 30 |
"""
|
| 31 |
parse a reader CCID descriptor and return a dictionnary
|
| 32 |
"""
|
| 33 |
reader_dict = {}
|
| 34 |
reader_file = open(path+reader)
|
| 35 |
for line in reader_file.readlines():
|
| 36 |
line = line[0:-1]
|
| 37 |
l = line.strip(" ").split(':')
|
| 38 |
if (len(l) > 1):
|
| 39 |
reader_dict[l[0]] = l[1].strip(" ")
|
| 40 |
reader_file.close()
|
| 41 |
return reader_dict
|
| 42 |
|
| 43 |
def parse_all(path, reader_list):
|
| 44 |
"""
|
| 45 |
parse each reader from list
|
| 46 |
return a dictionnary
|
| 47 |
"""
|
| 48 |
readers = {}
|
| 49 |
for reader in reader_list:
|
| 50 |
p = parse_reader(path, reader)
|
| 51 |
readers[reader] = p
|
| 52 |
|
| 53 |
return readers
|
| 54 |
|
| 55 |
def parse_ini(path, section):
|
| 56 |
"""
|
| 57 |
parse a foobas.ini file to extract all informations
|
| 58 |
"""
|
| 59 |
config = ConfigParser.ConfigParser()
|
| 60 |
# do not use the default case insensitive transform for key value
|
| 61 |
config.optionxform = str
|
| 62 |
config.read(section + ".ini")
|
| 63 |
reader_list = config.sections()
|
| 64 |
|
| 65 |
readers = parse_all(path, reader_list)
|
| 66 |
for r in readers.keys():
|
| 67 |
readers[r]['section'] = section
|
| 68 |
for o in config.options(r):
|
| 69 |
if o == 'features':
|
| 70 |
# for the features we use a list
|
| 71 |
readers[r][o] = [ config.get(r, o) ]
|
| 72 |
else:
|
| 73 |
readers[r][o] = config.get(r, o)
|
| 74 |
|
| 75 |
bPINSupport = int(readers[r]['bPINSupport'], 16)
|
| 76 |
if bPINSupport != 0 and not 'features' in readers[r]:
|
| 77 |
readers[r]['features'] = list()
|
| 78 |
if bPINSupport & 1:
|
| 79 |
readers[r]['features'].append("PIN Verification")
|
| 80 |
if bPINSupport & 2:
|
| 81 |
readers[r]['features'].append("PIN Modification")
|
| 82 |
|
| 83 |
#pp.pprint(readers["GemPCPinpad.txt"])
|
| 84 |
return readers
|
| 85 |
|
| 86 |
def check_list(path, reader_list):
|
| 87 |
"""
|
| 88 |
Check that all .txt files are listed
|
| 89 |
"""
|
| 90 |
cwd = os.getcwd()
|
| 91 |
os.chdir(path)
|
| 92 |
real_list = glob.glob("*.txt")
|
| 93 |
os.chdir(cwd)
|
| 94 |
|
| 95 |
# check that each reader file is listed
|
| 96 |
#print real_list
|
| 97 |
for r in reader_list:
|
| 98 |
#print "remove ", r
|
| 99 |
try:
|
| 100 |
real_list.remove(r)
|
| 101 |
except:
|
| 102 |
print "reader %s not yet listed" % r
|
| 103 |
|
| 104 |
# also remove the non-reader supported_readers.txt file
|
| 105 |
real_list.remove("supported_readers.txt")
|
| 106 |
|
| 107 |
# some USB descriptor are not listed in readers.txt?
|
| 108 |
if len(real_list) > 0:
|
| 109 |
raise Exception("readers %s are not listed" % real_list)
|
| 110 |
|
| 111 |
def check_supported(path, all_readers):
|
| 112 |
"""
|
| 113 |
Check that all .txt files are mentionned in supported_readers.txt
|
| 114 |
"""
|
| 115 |
supported_readers = file(path + "supported_readers.txt").readlines()
|
| 116 |
# convert in a long string and in uppercase
|
| 117 |
s = "".join(supported_readers).upper()
|
| 118 |
|
| 119 |
display = True
|
| 120 |
for r in all_readers.keys():
|
| 121 |
pattern = "%s:%s" % (all_readers[r]['idVendor'], all_readers[r]['idProduct'])
|
| 122 |
if not pattern.upper() in s:
|
| 123 |
if display:
|
| 124 |
print "Reader(s) not in supported_readers.txt"
|
| 125 |
display = False
|
| 126 |
print pattern + " " + r
|
| 127 |
#pp.pprint(supported_readers)
|
| 128 |
|
| 129 |
def get_by_manufacturer(readers):
|
| 130 |
"""
|
| 131 |
return a dict of the readers grouped by manufacturer
|
| 132 |
d['manufacturer'] is a list of the manufacturer's readers
|
| 133 |
"""
|
| 134 |
d = {}
|
| 135 |
for r in readers.keys():
|
| 136 |
d.setdefault(readers[r]['iManufacturer'], []).append(r)
|
| 137 |
return d
|
| 138 |
|
| 139 |
def generate_page(section, title, comment, readers):
|
| 140 |
"""
|
| 141 |
generate a web page for the corresponding section
|
| 142 |
"""
|
| 143 |
# sort the readers by manufacturers
|
| 144 |
manufacturer_readers = get_by_manufacturer(readers)
|
| 145 |
manufacturers = list(manufacturer_readers)
|
| 146 |
# sort the manufacturers list alphabetically
|
| 147 |
manufacturers.sort(key=str.lower)
|
| 148 |
|
| 149 |
template = templayer.HTMLTemplate("webpage.template")
|
| 150 |
file_writer = template.start_file(file=file(section + ".html", "w"))
|
| 151 |
main_layer = file_writer.open(date=time.asctime(),
|
| 152 |
title=title, comment=comment, section=section)
|
| 153 |
|
| 154 |
# for each manufacturer
|
| 155 |
for m in manufacturers:
|
| 156 |
main_layer.write_layer('manufacturer', manufacturer=m)
|
| 157 |
|
| 158 |
# for each reader
|
| 159 |
for r in sorted(manufacturer_readers[m]):
|
| 160 |
note_layer = main_layer.open_layer('reader',
|
| 161 |
manufacturer = m,
|
| 162 |
product = readers[r]['iProduct'],
|
| 163 |
idVendor = readers[r]['idVendor'],
|
| 164 |
idProduct = readers[r]['idProduct'],
|
| 165 |
image = "img/" + readers[r].get('image', "no_image.png"))
|
| 166 |
|
| 167 |
note_layer.write_layer('descriptor', descriptor = "readers/%s" % r)
|
| 168 |
|
| 169 |
url = readers[r].get('url', "")
|
| 170 |
if url:
|
| 171 |
note_layer.write_layer('url',
|
| 172 |
url = url,
|
| 173 |
manufacturer = m,
|
| 174 |
product = readers[r]['iProduct'])
|
| 175 |
|
| 176 |
features = ", ".join(readers[r].get('features', ""))
|
| 177 |
if features:
|
| 178 |
note_layer.write_layer('features', features = features)
|
| 179 |
|
| 180 |
note = readers[r].get('note', "").split('\n')
|
| 181 |
for n in note:
|
| 182 |
note_layer.write_layer('note', contents = n)
|
| 183 |
|
| 184 |
file_writer.close()
|
| 185 |
|
| 186 |
def generate_table(readers, field, index, fields):
|
| 187 |
"""
|
| 188 |
generate a web page with all the reader attributes
|
| 189 |
readers are in the order given by index
|
| 190 |
"""
|
| 191 |
header = """<?xml version="1.0" encoding="UTF-8"?>
|
| 192 |
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN">
|
| 193 |
<html>
|
| 194 |
<head>
|
| 195 |
<title>%s</title>
|
| 196 |
<link rel="stylesheet" type="text/css" href="default.css">
|
| 197 |
</head>
|
| 198 |
<body>"""
|
| 199 |
footer = """
|
| 200 |
<script src="http://www.google-analytics.com/urchin.js" type="text/javascript"> </script> <script type="text/javascript"> _uacct = "UA-2404298-2"; urchinTracker(); </script>
|
| 201 |
</body>
|
| 202 |
</html>"""
|
| 203 |
documentation = """
|
| 204 |
<p>Click on the column header to sort by that column.</p>
|
| 205 |
<p>The background color indicates the section of the reader:
|
| 206 |
<table border="1">
|
| 207 |
<tr><td>supported</td>
|
| 208 |
<td bgcolor="#aff">should work</td>
|
| 209 |
<td bgcolor="#faa">unsupported</td></tr></table></p>
|
| 210 |
"""
|
| 211 |
|
| 212 |
file = open(field + ".html", "w")
|
| 213 |
title = "Readers sorted by '%s' field" % field
|
| 214 |
file.write(header % title)
|
| 215 |
|
| 216 |
file.write("<h1>" + title + "</h1>")
|
| 217 |
file.write(documentation)
|
| 218 |
|
| 219 |
file.write('<table border="1" summary="">\n')
|
| 220 |
|
| 221 |
file.write('<tr>')
|
| 222 |
file.write("<th>#</th>")
|
| 223 |
for f in fields:
|
| 224 |
file.write("<th><a href='%s'>%s</a></th>" % (f+".html", f))
|
| 225 |
file.write('</tr>\n')
|
| 226 |
|
| 227 |
num = 0
|
| 228 |
for r in index:
|
| 229 |
num += 1
|
| 230 |
if readers[r]['section'] == "unsupported":
|
| 231 |
file.write('<tr bgcolor="#faa">')
|
| 232 |
elif readers[r]['section'] == "shouldwork":
|
| 233 |
file.write('<tr bgcolor="#aff">')
|
| 234 |
else:
|
| 235 |
file.write('<tr>')
|
| 236 |
file.write("<td>%d</td>" % num)
|
| 237 |
for f in fields:
|
| 238 |
if f == 'iProduct':
|
| 239 |
file.write("<td><a href='%s.html#%s%s'>%s</a></td>" % (readers[r]['section'], readers[r]['idVendor'], readers[r]['idProduct'], readers[r][f]))
|
| 240 |
elif f == 'iManufacturer':
|
| 241 |
file.write('<td><a onmouseover="">%s<img src="%s"></td>' % (readers[r][f], "img/" + readers[r].get('image', "no_image.png")))
|
| 242 |
else:
|
| 243 |
file.write("<td>%s</td>" % readers[r].get(f, ""))
|
| 244 |
file.write('</tr>\n')
|
| 245 |
|
| 246 |
file.write('</table>\n')
|
| 247 |
|
| 248 |
file.write("<hr><p>Generated: %s</p>" % time.asctime())
|
| 249 |
|
| 250 |
file.write(footer)
|
| 251 |
file.close()
|
| 252 |
|
| 253 |
def generate_tables(readers):
|
| 254 |
"""
|
| 255 |
generate all the web page tables with all the fields values
|
| 256 |
"""
|
| 257 |
fields = [ 'section', 'iManufacturer', 'iProduct', 'idVendor',
|
| 258 |
'idProduct', 'bNumEndpoints', 'bInterfaceClass', 'bcdCCID',
|
| 259 |
'bMaxSlotIndex', 'bVoltageSupport', 'dwProtocols',
|
| 260 |
'dwDefaultClock', 'dwMaximumClock', 'dwDataRate',
|
| 261 |
'dwMaxDataRate', 'dwMaxIFSD', 'dwSynchProtocols',
|
| 262 |
'dwMechanical', 'dwFeatures', 'dwMaxCCIDMessageLength',
|
| 263 |
'bClassGetResponse', 'bClassEnveloppe', 'wLcdLayout',
|
| 264 |
'bPINSupport', 'bMaxCCIDBusySlots', 'features' ]
|
| 265 |
|
| 266 |
for f in fields:
|
| 267 |
index = list()
|
| 268 |
for r in readers.keys():
|
| 269 |
index.append([readers[r].get(f, ""), r])
|
| 270 |
if f == 'section':
|
| 271 |
# hack to sort in the order supported, shouldwork, unsupported
|
| 272 |
index = [(s.replace('supported', 'asupported'),r) for s,r in index]
|
| 273 |
if f in ['dwDefaultClock', 'dwMaximumClock', 'dwDataRate',
|
| 274 |
'dwMaxDataRate', 'dwMaxIFSD', 'dwMaxCCIDMessageLength']:
|
| 275 |
# convert from text to decimal
|
| 276 |
index = [(float(s.split(' ')[0])*1000,r) for s,r in index]
|
| 277 |
index.sort()
|
| 278 |
sorted_fields = list(fields)
|
| 279 |
sorted_fields.remove(f)
|
| 280 |
sorted_fields.insert(0, f)
|
| 281 |
generate_table(readers, f, [r for f, r in index], sorted_fields)
|
| 282 |
|
| 283 |
if __name__ == "__main__":
|
| 284 |
path = "../trunk/Drivers/ccid/readers/"
|
| 285 |
|
| 286 |
supported_readers = parse_ini(path, "supported")
|
| 287 |
shouldwork_readers = parse_ini(path, "shouldwork")
|
| 288 |
unsupported_readers = parse_ini(path, "unsupported")
|
| 289 |
|
| 290 |
# all_readers contain the union of the 3 lists
|
| 291 |
all_readers = dict(supported_readers)
|
| 292 |
all_readers.update(shouldwork_readers)
|
| 293 |
all_readers.update(unsupported_readers)
|
| 294 |
|
| 295 |
check_list(path, all_readers.keys())
|
| 296 |
check_supported(path, all_readers)
|
| 297 |
|
| 298 |
generate_page("supported", "Supported CCID readers/ICCD tokens", "If you are a reader manufacturer and your reader is not listed here then contact me at ludovic.rousseau@free.fr", supported_readers)
|
| 299 |
generate_page("shouldwork", "Should work but untested by me", "The CCID readers and ICCD tokens listed bellow should work with the driver but have not be validated by me. I would like to get these readers to perform test and validation and move them in the supported list above. If you are one of the manufacturers, please, contact me at ludovic.rousseau@free.fr.", shouldwork_readers)
|
| 300 |
generate_page("unsupported", "Unsupported or partly supported CCID readers", "These readers have problems or serious limitations.", unsupported_readers)
|
| 301 |
|
| 302 |
generate_tables(all_readers)
|