/[pcsclite]/website/matrix.py
ViewVC logotype

Contents of /website/matrix.py

Parent Directory Parent Directory | Revision Log Revision Log


Revision 5756 - (show annotations) (download) (as text)
Sun May 15 15:29:56 2011 UTC (2 years, 1 month ago) by rousseau
File MIME type: text/x-python
File size: 20914 byte(s)
Some more pylint improvements
1 #!/usr/bin/env python
2
3 """
4 # matrix.py generate a matrix of all readers characteristics
5 # Copyright (C) 2009-2011 Ludovic Rousseau
6 #
7 # This program is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 2 of the License, or
10 # (at your option) any later version.
11 #
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
16 #
17 # You should have received a copy of the GNU General Public License along
18 # with this program; if not, write to the Free Software Foundation, Inc.,
19 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 """
21
22 # $Id$
23
24 import glob
25 import os
26 import ConfigParser
27 #import pprint
28 import templayer
29 import time
30 import re
31
32 #pp = pprint.PrettyPrinter(indent=4)
33
34 CCID_CLASS_CHARACTER = 0x00000000
35 CCID_CLASS_TPDU = 0x00010000
36 CCID_CLASS_SHORT_APDU = 0x00020000
37 CCID_CLASS_EXTENDED_APDU = 0x00040000
38 CCID_CLASS_EXCHANGE_MASK = 0x00070000
39
40
41 def html_escape(text):
42 """Produce entities within text."""
43 html_escape_table = {
44 "&": "&",
45 '"': """,
46 ">": ">",
47 "<": "&lt;",
48 }
49
50 result = list()
51 for char in text:
52 result.append(html_escape_table.get(char, char))
53 return "".join(result)
54
55
56 def parse_interface(lines):
57 """
58 parse a reader CCID interface and return a dictionnary
59 """
60 reader_dict = {}
61 reader_dict['features'] = list()
62 reader_dict['limitations'] = list()
63
64 for line in lines:
65 # remove the \n
66 line = line[0:-1]
67
68 l = line.strip(" ").split(':')
69 if (len(l) > 1):
70 reader_dict[l[0]] = l[1].strip(" ")
71
72 dwFeatures = int(reader_dict['dwFeatures'], 16)
73 if (dwFeatures & CCID_CLASS_EXCHANGE_MASK) == CCID_CLASS_SHORT_APDU:
74 reader_dict['limitations'].append("No extended APDU")
75 return reader_dict
76
77
78 def parse_reader(path, reader):
79 """
80 parse a reader CCID descriptor and return a list of interfaces
81 """
82 lines = open(path + reader).readlines()
83
84 # split the .txt file per interface
85 interfaces = list()
86 interface = list()
87 for line in lines:
88 if line.split(':')[0] == ' idVendor':
89 interface = list()
90 interfaces.append(interface)
91 interface.append(line)
92
93 return interfaces
94
95
96 def parse_all(path, reader_list):
97 """
98 parse each reader from list
99 return a dictionnary
100 """
101 readers = {}
102 for reader in reader_list:
103 # split the .txt file per interface
104 interfaces = parse_reader(path, reader)
105
106 for interface in interfaces:
107 parse = parse_interface(interface)
108 if reader in readers:
109 # second interface
110 readers[reader]['features'].append("Multi interface reader")
111 parse['features'].append("Second interface")
112 readers[reader]['interface'] = parse
113 else:
114 readers[reader] = parse
115
116 return readers
117
118
119 def parse_ini(path, section):
120 """
121 parse a foobar.ini file to extract all informations
122 """
123 config = ConfigParser.ConfigParser()
124 # do not use the default case insensitive transform for key value
125 config.optionxform = str
126 config.read(section + ".ini")
127 reader_list = config.sections()
128
129 readers = parse_all(path, reader_list)
130 for reader in readers.keys():
131 readers[reader]['section'] = section
132
133 for option in config.options(reader):
134 if option == 'features':
135 # for the features we use a list
136 readers[reader][option].append(config.get(reader, option))
137 else:
138 readers[reader][option] = config.get(reader, option)
139
140 bPINSupport = int(readers[reader]['bPINSupport'], 16)
141 if bPINSupport & 1:
142 readers[reader]['features'].append("PIN Verification")
143 if bPINSupport & 2:
144 readers[reader]['features'].append("PIN Modification")
145 if int(readers[reader]['dwFeatures'], 16) & 0x0800:
146 readers[reader]['features'].append("ICCD")
147
148 if 'interface' in readers[reader]:
149 second_interface = readers[reader]['interface']
150 first_interface = readers[reader]
151 second_interface['section'] = section
152 for elt in first_interface['features']:
153 second_interface['features'].append(elt)
154 if 'image' in first_interface:
155 second_interface['image'] = first_interface['image']
156
157 #pp.pprint(readers["GemPCPinpad.txt"])
158 return readers
159
160
161 def check_list(path, reader_list):
162 """
163 Check that all .txt files are listed
164 """
165 cwd = os.getcwd()
166 os.chdir(path)
167 real_list = glob.glob("*.txt")
168 os.chdir(cwd)
169
170 # check that each reader file is listed
171 #print real_list
172 for reader in reader_list:
173 #print "remove ", reader
174 try:
175 real_list.remove(reader)
176 except ValueError:
177 print "reader %s not yet listed" % reader
178
179 # also remove the non-reader supported_readers.txt file
180 real_list.remove("supported_readers.txt")
181
182 # some USB descriptor are not listed in readers.txt?
183 if len(real_list) > 0:
184 print "Reader(s) not listed in any .ini file:"
185 print "\n".join(real_list)
186 print ""
187
188
189 def check_supported(path, all_readers):
190 """
191 Check that all .txt files are mentionned in supported_readers.txt
192 """
193 supported_readers = file(path + "supported_readers.txt").readlines()
194 # convert in a long string and in uppercase
195 supported = "".join(supported_readers).upper()
196
197 unlisted = list()
198 for reader in all_readers.keys():
199 pattern = "%s:%s" % (all_readers[reader]['idVendor'],
200 all_readers[reader]['idProduct'])
201 if not pattern.upper() in supported:
202 unlisted.append(pattern + " " + reader)
203
204 if len(unlisted) > 0:
205 print "Reader(s) not in supported_readers.txt"
206 print "\n".join(unlisted)
207 print ""
208 #pp.pprint(supported_readers)
209
210
211 def check_descriptions(path, all_readers):
212 """
213 Check that all readers mentionned in supported_readers.txt have a
214 .txt descriptor
215 """
216 supported_readers = file(path + "supported_readers.txt").readlines()
217
218 unlisted = list()
219 for line in supported_readers:
220 # skip comments
221 if line.startswith("#"):
222 # but get commented readers
223 if line.startswith("#0"):
224 # remove the leading #
225 line = line[1:]
226 else:
227 continue
228
229 # remove newline
230 line = line.rstrip()
231
232 # skip empty lines
233 if line is "":
234 continue
235
236 (vendor, product, name) = line.split(":")
237 vendor = int(vendor, 16)
238 product = int(product, 16)
239 found = False
240 for reader in all_readers.keys():
241 reader = all_readers[reader]
242 if vendor == int(reader['idVendor'], 16) and product == int(reader['idProduct'], 16):
243 found = True
244 if not found:
245 unlisted.append(line)
246
247 if len(unlisted) > 0:
248 print "Reader(s) without a .txt description"
249 print "\n".join(unlisted)
250 print ""
251 #pp.pprint(supported_readers)
252
253
254 def get_driver_version(readers):
255 """
256 set the 'release' field for each reader
257 """
258 changelog = get_changelog()
259
260 for reader in readers.keys():
261 rev = get_driver_revision(reader, changelog)
262 readers[reader]['release'] = driver_revision_to_version(rev)
263 if 'interface' in readers[reader]:
264 readers[reader]['interface']['release'] = readers[reader]['release']
265
266
267 def driver_revision_to_version(rev):
268 """
269 convert a SVN revision in the first release containing this revision
270 """
271 history = [
272 #[ SVN revision, CCID release ]
273 [273, "0.1.0"],
274 [342, "0.2.0"],
275 [423, "0.3.0"],
276 [467, "0.3.1"],
277 [552, "0.3.2"],
278 [697, "0.4.0"],
279 [703, "0.4.1"],
280 [1015, "0.9.0"],
281 [1018, "0.9.1"],
282 [1186, "0.9.2"],
283 [1400, "0.9.3"],
284 [1761, "0.9.4"],
285 [1911, "1.0.0"],
286 [2020, "1.0.1"],
287 [2135, "1.1.0"],
288 [2345, "1.2.0"],
289 [2363, "1.2.1"],
290 [2522, "1.3.0"],
291 [2692, "1.3.1"],
292 [2755, "1.3.2"],
293 [2796, "1.3.3"],
294 [2809, "1.3.4"],
295 [2842, "1.3.5"],
296 [2924, "1.3.6"],
297 [2985, "1.3.7"],
298 [3033, "1.3.8"],
299 [3208, "1.3.9"],
300 [3338, "1.3.10"],
301 [4347, "1.3.11"],
302 [4931, "1.3.12"],
303 [4979, "1.3.13"],
304 [5108, "1.4.0"],
305 [5430, "1.4.1"],
306 [5626, "1.4.2"],
307 [5688, "1.4.3"],
308 [5742, "1.4.4"]]
309 for release in history:
310 if rev <= release[0]:
311 return release[1]
312 return "SVN"
313
314
315 def get_changelog():
316 """
317 read a complete svn2cl Changelog file and merge commits on one line
318 """
319 lines = open("ccid/readers/ChangeLog").readlines()
320 changelog = list()
321 tmp = list()
322
323 for line in lines:
324 # the lines starts with a year (2000-2099)
325 if line.startswith('20'):
326 changelog.append("".join(tmp).replace('\n', ''))
327 tmp = [line]
328 else:
329 tmp.append(line)
330
331 # add the last line
332 changelog.append("".join(tmp).replace('\n', ''))
333
334 return changelog
335
336
337 def get_driver_revision(reader, changelog):
338 """
339 search a log line containing the reader string
340 """
341 found = None
342 for line in changelog:
343 if reader in line:
344 found = line
345
346 if found:
347 result = re.search('\\* \\[r(\d*)\\]', found)
348 if result:
349 # revision is the inner matching pattern
350 return int(result.group(1))
351 else:
352 print "reader %s not found in ChangeLog" % reader
353 # fake SVN revision number high enough to be considered as
354 # unreleased
355 return 999999999
356
357
358 def get_by_manufacturer(readers):
359 """
360 return a dict of the readers grouped by manufacturer
361 by_manuf['manufacturer'] is a list of the manufacturer's readers
362 """
363 by_manuf = dict()
364 for reader in readers.keys():
365 by_manuf.setdefault(readers[reader]['iManufacturer'], []).append(reader)
366 return by_manuf
367
368
369 def generate_page(section, title, comment, readers):
370 """
371 generate a web page for the corresponding section
372 """
373 # sort the readers by manufacturers
374 manufacturer_readers = get_by_manufacturer(readers)
375 manufacturers = list(manufacturer_readers)
376 # sort the manufacturers list alphabetically
377 manufacturers.sort(key=str.lower)
378
379 template = templayer.HTMLTemplate("webpage.template")
380 file_writer = template.start_file(file=file("ccid/" + section + ".html", "w"))
381 main_layer = file_writer.open(date=time.asctime(),
382 title=title, comment=comment, section=section)
383
384 # for each manufacturer
385 for manufacturer in manufacturers:
386 main_layer.write_layer('manufacturer', manufacturer=manufacturer)
387
388 # for each reader
389 for reader in sorted(manufacturer_readers[manufacturer]):
390 note_layer = main_layer.open_layer('reader',
391 manufacturer=manufacturer,
392 product=readers[reader]['iProduct'],
393 idVendor=readers[reader]['idVendor'],
394 idProduct=readers[reader]['idProduct'],
395 image="img/" + readers[reader].get('image', "no_image.png"))
396
397 note_layer.write_layer('descriptor', descriptor="readers/%s"
398 % reader)
399
400 url = readers[reader].get('url', "")
401 if url:
402 note_layer.write_layer('url',
403 url=url,
404 manufacturer=manufacturer,
405 product=readers[reader]['iProduct'])
406
407 features = ", ".join(readers[reader]['features'])
408 if features:
409 note_layer.write_layer('features', features=features)
410
411 limitations = ", ".join(readers[reader]['limitations'])
412 if limitations:
413 note_layer.write_layer('limitations', limitations=limitations)
414
415 notes = readers[reader].get('note', "").split('\n')
416 for note in notes:
417 note_layer.write_layer('note', contents=note)
418
419 note_layer.write_layer('release',
420 release=readers[reader]['release'])
421
422 file_writer.close()
423
424
425 def add_line(file_desc, num, reader, fields):
426 """
427 Add a line to the matrix
428 """
429 # define color of the line
430 if num % 2:
431 # even line number
432 color = reader['section']
433 else:
434 # odd line number
435 color = reader['section'] + '_odd'
436 file_desc.write('<tr class="%s">' % color)
437
438 file_desc.write("<td>%d</td>" % num)
439 for field in fields:
440 if field == 'iProduct':
441 file_desc.write("<td><a href='%s.html#%s%s'>%s</a></td>" %
442 (reader['section'], reader['idVendor'],
443 reader['idProduct'], reader[field]))
444 elif field == 'image':
445 file_desc.write('<td><img src="%s" height="100" alt="image"></td>' % ("img/" + reader.get('image', "no_image.png")))
446 elif field == 'features':
447 file_desc.write("<td>%s</td>" % ", ".join(reader[field]))
448 else:
449 file_desc.write("<td>%s</td>" % html_escape(reader.get(field, "")))
450 file_desc.write('</tr>\n')
451
452
453 def generate_table(readers, field, index, fields):
454 """
455 generate a web page with all the reader attributes
456 readers are in the order given by index
457 """
458
459 header = """<?xml version="1.0" encoding="UTF-8"?>
460 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
461 <html>
462 <head>
463 <title>%s</title>
464 <link rel="stylesheet" type="text/css" href="default.css">
465 <link rel="stylesheet" type="text/css" href="matrix.css">
466 <script type="text/javascript">
467 /* <![CDATA[ */
468 (function() {
469 var s = document.createElement('script'), t = document.getElementsByTagName('script')[0];
470
471 s.type = 'text/javascript';
472 s.async = true;
473 s.src = 'http://api.flattr.com/js/0.5.0/load.js?mode=auto';
474
475 t.parentNode.insertBefore(s, t);
476 })();
477 /* ]]> */
478 </script>
479 </head>
480 <body>"""
481
482 footer = """
483 <p>
484 <a href="http://validator.w3.org/check/referer"><img src="http://www.w3.org/Icons/valid-html401" alt="Valid HTML 4.01!"></a>
485 <a href="http://jigsaw.w3.org/css-validator/"><img src="http://jigsaw.w3.org/css-validator/images/vcss" alt="Valid CSS!"></a>
486 </p>
487
488 <script type="text/javascript">
489 var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www.");
490 document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E"));
491 </script>
492 <script type="text/javascript">
493 try {
494 var pageTracker = _gat._getTracker("UA-2404298-2");
495 pageTracker._trackPageview();
496 } catch(err) {}</script>
497 <p>Ludovic Rousseau</p>
498 </body>
499 </html>"""
500
501 documentation = """
502 <ul>
503 <li>Click on the column header to sort by that column.</li>
504 <li>The background color indicates the section of the reader:
505 <table border="1" summary="color codes">
506 <tr>
507 <td class="supported">supported</td>
508 <td class="shouldwork">should work</td>
509 <td class="unsupported">unsupported</td>
510 <td class="disabled">disabled</td>
511 </tr></table></li>
512 <li><a class="FlattrButton" style="display:none;"
513 href="http://pcsclite.alioth.debian.org/ccid.html"></a></li>
514 </ul>
515 """
516
517 file_desc = open("ccid/" + field + ".html", "w")
518 title = "Readers sorted by '%s' field" % field
519 file_desc.write(header % title)
520
521 file_desc.write("<h1>" + title + "</h1>")
522 file_desc.write(documentation)
523
524 file_desc.write('<table border="1" summary="">\n')
525
526 file_desc.write('<tr>')
527 file_desc.write("<th>#</th>")
528 for field in fields:
529 file_desc.write("<th><a href='%s'>%s</a></th>" % (field +
530 ".html", field))
531 file_desc.write('</tr>\n')
532
533 num = 0
534 for reader in index:
535 num += 1
536 add_line(file_desc, num, readers[reader], fields)
537
538 if 'interface' in readers[reader]:
539 num += 1
540 add_line(file_desc, num, readers[reader]['interface'], fields)
541
542 file_desc.write('</table>\n')
543
544 file_desc.write("<hr><p>Generated: %s</p>" % time.asctime())
545
546 file_desc.write(footer)
547 file_desc.close()
548
549
550 def generate_tables(readers):
551 """
552 generate all the web page tables with all the fields values
553 """
554 fields = ['section', 'iManufacturer', 'iProduct', 'image', 'idVendor',
555 'idProduct', 'iInterface', 'bNumEndpoints',
556 'bInterfaceClass', 'bcdCCID', 'bMaxSlotIndex',
557 'bVoltageSupport', 'dwProtocols', 'dwDefaultClock',
558 'dwMaximumClock', 'dwDataRate', 'dwMaxDataRate',
559 'dwMaxIFSD', 'dwSynchProtocols', 'dwMechanical',
560 'dwFeatures', 'dwMaxCCIDMessageLength', 'bClassGetResponse',
561 'bClassEnveloppe', 'wLcdLayout', 'bPINSupport',
562 'bMaxCCIDBusySlots', 'features', 'limitations', 'note', 'release']
563
564 for field in fields:
565 # create a list of tuples [field value, reader]
566 index = list()
567 for reader in readers.keys():
568 index.append([readers[reader].get(field, ""), reader])
569
570 if field == 'section':
571 # hack to sort in the order supported, shouldwork,
572 # unsupported, disabled
573 index = [(s.replace('supported', 'asupported'), reader)
574 for s, reader in index]
575 index = [(s.replace('disabled', 'zdisabled'), reader)
576 for s, reader in index]
577
578 if field in ['dwDefaultClock', 'dwMaximumClock', 'dwDataRate',
579 'dwMaxDataRate', 'dwMaxIFSD', 'dwMaxCCIDMessageLength']:
580 # convert from text to decimal
581 index = [(float(s.split(' ')[0]) * 1000, reader)
582 for s, reader in index]
583
584 if field == 'release':
585 index = [(release2int(s), reader) for s, reader in index]
586
587 # sort the index according to field value
588 index.sort()
589
590 # put the main field in column 0
591 sorted_fields = list(fields)
592 sorted_fields.remove(field)
593 sorted_fields.insert(0, field)
594
595 # generate the table
596 generate_table(readers, field, [reader for field, reader in index], sorted_fields)
597
598
599 def release2int(release):
600 """
601 Convert a release value "a.b.c" in c + b * 1000 + a * 1000 * 1000
602 """
603 rel = release.split('.')
604 if len(rel) > 1:
605 return int(rel[2]) + int(rel[1]) * 1000 + int(rel[0]) * 1000 * 1000
606 else:
607 # SVN version
608 return 99 * 1000 * 1000
609
610
611 def main():
612 """
613 Generate every thing
614 """
615 path = "ccid/readers/"
616
617 supported_readers = parse_ini(path, "supported")
618 shouldwork_readers = parse_ini(path, "shouldwork")
619 unsupported_readers = parse_ini(path, "unsupported")
620 disabled_readers = parse_ini(path, "disabled")
621
622 # all_readers contain the union of the 3 lists
623 all_readers = dict(supported_readers)
624 all_readers.update(shouldwork_readers)
625 all_readers.update(unsupported_readers)
626 all_readers.update(disabled_readers)
627
628 check_list(path, all_readers.keys())
629 check_supported(path, all_readers)
630 check_descriptions(path, all_readers)
631
632 get_driver_version(supported_readers)
633 get_driver_version(shouldwork_readers)
634 get_driver_version(unsupported_readers)
635 get_driver_version(disabled_readers)
636
637 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)
638 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)
639 generate_page("unsupported", "Unsupported or partly supported CCID readers", "These readers have problems or serious limitations.", unsupported_readers)
640 generate_page("disabled", "Disabled CCID readers", "These readers are not even recognized by the driver because they are too bogus or because they are supported by another driver.", disabled_readers)
641
642 generate_tables(all_readers)
643
644
645 if __name__ == "__main__":
646 main()

Properties

Name Value
svn:executable *
svn:keywords Id

  ViewVC Help
Powered by ViewVC 1.1.5