blob: 7868b2e60fe579cc86658745dfebd64dcb407605 [file] [log] [blame]
Brian Silverman84b22232019-01-25 20:29:29 -08001# pycrc -- parameterisable CRC calculation utility and C source code generator
2#
3# Copyright (c) 2006-2017 Thomas Pircher <tehpeh-web@tty1.net>
4#
5# Permission is hereby granted, free of charge, to any person obtaining a copy
6# of this software and associated documentation files (the "Software"), to
7# deal in the Software without restriction, including without limitation the
8# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
9# sell copies of the Software, and to permit persons to whom the Software is
10# furnished to do so, subject to the following conditions:
11#
12# The above copyright notice and this permission notice shall be included in
13# all copies or substantial portions of the Software.
14#
15# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21# IN THE SOFTWARE.
22
23
24"""
25Option parsing library for pycrc.
26use as follows:
27
28 from pycrc.opt import Options
29
30 opt = Options()
31 opt.parse(sys.argv[1:])
32"""
33
34from optparse import OptionParser, Option, OptionValueError
35from copy import copy
36import sys
37from pycrc.models import CrcModels
38
39
40class Options(object):
41 """
42 The options parsing and validating class.
43 """
44 # pylint: disable=too-many-instance-attributes, too-few-public-methods
45
46 # Bitmap of the algorithms
47 algo_none = 0x00
48 algo_bit_by_bit = 0x01
49 algo_bit_by_bit_fast = 0x02
50 algo_table_driven = 0x08
51
52 action_check_str = 0x01
53 action_check_hex_str = 0x02
54 action_check_file = 0x03
55 action_generate_h = 0x04
56 action_generate_c = 0x05
57 action_generate_c_main = 0x06
58 action_generate_table = 0x07
59
60
61 def __init__(self, progname='pycrc', version=None, url=None):
62 self.program_name = progname
63 self.version = version
64 self.version_str = "{0:s} v{1:s}".format(progname, version)
65 self.web_address = url
66
67 self.width = None
68 self.poly = None
69 self.reflect_in = None
70 self.xor_in = None
71 self.reflect_out = None
72 self.xor_out = None
73 self.tbl_idx_width = 8
74 self.tbl_width = 1 << self.tbl_idx_width
75 self.slice_by = 1
76 self.verbose = False
77 self.check_string = "123456789"
78 self.msb_mask = None
79 self.mask = None
80
81 self.algorithm = self.algo_none
82 self.symbol_prefix = "crc_"
83 self.crc_type = None
84 self.include_files = []
85 self.output_file = None
86 self.action = self.action_check_str
87 self.check_file = None
88 self.c_std = None
89 self.undefined_crc_parameters = False
90
91
92 def parse(self, argv=None):
93 """
94 Parses and validates the options given as arguments
95 """
96 # pylint: disable=too-many-branches, too-many-statements
97
98 usage = """python %prog [OPTIONS]
99
100To calculate the checksum of a string or hexadecimal data:
101 python %prog [model] --check-string "123456789"
102 python %prog [model] --check-hexstring "313233343536373839"
103
104To calculate the checksum of a file:
105 python %prog [model] --check-file filename
106
107To generate the C source code and write it to filename:
108 python %prog [model] --generate c -o filename
109
110The model can be defined either with the --model switch or by specifying each
111of the following parameters:
112 --width --poly --reflect-in --xor-in --reflect-out --xor-out"""
113
114 models = CrcModels()
115 model_list = ", ".join(models.names())
116 parser = OptionParser(option_class=MyOption, usage=usage, version=self.version_str)
117 parser.add_option(
118 "-v", "--verbose",
119 action="store_true", dest="verbose", default=False,
120 help="be more verbose; print the value of the parameters "
121 "and the chosen model to stdout")
122 parser.add_option(
123 "--check-string",
124 action="store", type="string", dest="check_string",
125 help="calculate the checksum of a string (default: '123456789')",
126 metavar="STRING")
127 parser.add_option(
128 "--check-hexstring",
129 action="store", type="string", dest="check_hexstring",
130 help="calculate the checksum of a hexadecimal number string",
131 metavar="STRING")
132 parser.add_option(
133 "--check-file",
134 action="store", type="string", dest="check_file",
135 help="calculate the checksum of a file",
136 metavar="FILE")
137 parser.add_option(
138 "--generate",
139 action="store", type="string", dest="generate", default=None,
140 help="generate C source code; choose the type from {h, c, c-main, table}",
141 metavar="CODE")
142 parser.add_option(
143 "--std",
144 action="store", type="string", dest="c_std", default="C99",
145 help="choose the C dialect of the generated code from {C89, ANSI, C99}",
146 metavar="STD")
147 parser.add_option(
148 "--algorithm",
149 action="store", type="string", dest="algorithm", default="all",
150 help="choose an algorithm from "
151 "{bit-by-bit, bbb, bit-by-bit-fast, bbf, table-driven, tbl, all}",
152 metavar="ALGO")
153 parser.add_option(
154 "--model",
155 action="callback", callback=_model_cb, type="string", dest="model", default=None,
156 help="choose a parameter set from {{{0:s}}}".format(model_list),
157 metavar="MODEL")
158 parser.add_option(
159 "--width",
160 action="store", type="hex", dest="width",
161 help="use NUM bits in the polynomial",
162 metavar="NUM")
163 parser.add_option(
164 "--poly",
165 action="store", type="hex", dest="poly",
166 help="use HEX as polynomial",
167 metavar="HEX")
168 parser.add_option(
169 "--reflect-in",
170 action="store", type="bool", dest="reflect_in",
171 help="reflect the octets in the input message",
172 metavar="BOOL")
173 parser.add_option(
174 "--xor-in",
175 action="store", type="hex", dest="xor_in",
176 help="use HEX as initial value",
177 metavar="HEX")
178 parser.add_option(
179 "--reflect-out",
180 action="store", type="bool", dest="reflect_out",
181 help="reflect the resulting checksum before applying the --xor-out value",
182 metavar="BOOL")
183 parser.add_option(
184 "--xor-out",
185 action="store", type="hex", dest="xor_out",
186 help="xor the final CRC value with HEX",
187 metavar="HEX")
188 parser.add_option(
189 "--slice-by",
190 action="store", type="int", dest="slice_by",
191 help="read NUM bytes at a time from the input. NUM must be one of the values {4, 8, 16}",
192 metavar="NUM")
193 parser.add_option(
194 "--table-idx-width",
195 action="store", type="int", dest="table_idx_width",
196 help="use NUM bits to index the CRC table; NUM must be one of the values {1, 2, 4, 8}",
197 metavar="NUM")
198 parser.add_option(
199 "--force-poly",
200 action="store_true", dest="force_poly", default=False,
201 help="override any errors about possibly unsuitable polynoms")
202 parser.add_option(
203 "--symbol-prefix",
204 action="store", type="string", dest="symbol_prefix",
205 help="when generating source code, use STRING as prefix to the exported C symbols",
206 metavar="STRING")
207 parser.add_option(
208 "--crc-type",
209 action="store", type="string", dest="crc_type",
210 help="when generating source code, use STRING as crc_t type",
211 metavar="STRING")
212 parser.add_option(
213 "--include-file",
214 action="append", type="string", dest="include_files",
215 help="when generating source code, include also FILE as header file; "
216 "can be specified multiple times",
217 metavar="FILE")
218 parser.add_option(
219 "-o", "--output",
220 action="store", type="string", dest="output_file",
221 help="write the generated code to file instead to stdout",
222 metavar="FILE")
223
224 (options, args) = parser.parse_args(argv)
225
226 if options.c_std != None:
227 std = options.c_std.upper()
228 if std == "ANSI" or std == "C89":
229 self.c_std = "C89"
230 elif std == "C99":
231 self.c_std = std
232 else:
233 self.__error("unknown C standard {0:s}".format(options.c_std))
234
235 undefined_params = []
236 if options.width != None:
237 self.width = options.width
238 else:
239 undefined_params.append("--width")
240 if options.poly != None:
241 self.poly = options.poly
242 else:
243 undefined_params.append("--poly")
244 if options.reflect_in != None:
245 self.reflect_in = options.reflect_in
246 else:
247 undefined_params.append("--reflect-in")
248 if options.xor_in != None:
249 self.xor_in = options.xor_in
250 else:
251 undefined_params.append("--xor-in")
252 if options.reflect_out != None:
253 self.reflect_out = options.reflect_out
254 else:
255 undefined_params.append("--reflect-out")
256 if options.xor_out != None:
257 self.xor_out = options.xor_out
258 else:
259 undefined_params.append("--xor-out")
260
261 if options.table_idx_width != None:
262 if options.table_idx_width in set((1, 2, 4, 8)):
263 self.tbl_idx_width = options.table_idx_width
264 self.tbl_width = 1 << options.table_idx_width
265 else:
266 self.__error("unsupported table-idx-width {0:d}".format(options.table_idx_width))
267
268 if self.poly != None and self.poly % 2 == 0 and not options.force_poly:
269 self.__error("even polinomials are not allowed by default. Use --force-poly to override this.")
270
271 if self.width != None:
272 if self.width <= 0:
273 self.__error("Width must be strictly positive")
274 self.msb_mask = 0x1 << (self.width - 1)
275 self.mask = ((self.msb_mask - 1) << 1) | 1
276 if self.poly != None and self.poly >> (self.width + 1) != 0 and not options.force_poly:
277 self.__error("the polynomial is wider than the supplied Width. Use --force-poly to override this.")
278 if self.poly != None:
279 self.poly = self.poly & self.mask
280 if self.xor_in != None:
281 self.xor_in = self.xor_in & self.mask
282 if self.xor_out != None:
283 self.xor_out = self.xor_out & self.mask
284 else:
285 self.msb_mask = None
286 self.mask = None
287
288 if self.width == None or \
289 self.poly == None or \
290 self.reflect_in == None or \
291 self.xor_in == None or \
292 self.reflect_out == None or \
293 self.xor_out == None:
294 self.undefined_crc_parameters = True
295 else:
296 self.undefined_crc_parameters = False
297
298 if options.slice_by != None:
299 if options.slice_by in set((4, 8, 16)):
300 self.slice_by = options.slice_by
301 else:
302 self.__error("unsupported slice-by {0:d}".format(options.slice_by))
303 if self.undefined_crc_parameters:
304 self.__error("slice-by is only implemented for fully defined models")
305 if self.tbl_idx_width != 8:
306 self.__error("slice-by is only implemented for table-idx-width=8")
307 # FIXME tp: Fix corner cases and disable the following tests
308 if self.width < 8:
309 self.__warning("disabling slice-by for width {0}".format(self.width))
310 self.slice_by = 1
311 if self.width < 16:
312 self.__warning("disabling slice-by for width {0}".format(self.width))
313 self.slice_by = 1
314 if self.width > 32:
315 self.__warning("disabling slice-by for width {0}".format(self.width))
316 self.slice_by = 1
317 if not self.reflect_in:
318 self.__warning("disabling slice-by for non-reflected algorithm")
319 self.slice_by = 1
320# FIXME tp: reintroduce this?
321# if self.width % 8 != 0:
322# self.__error("slice-by is only implemented for width multiples of 8")
323# if options.slice_by < self.width / 8:
324# self.__error("slice-by must be greater or equal width / 8")
325 if self.c_std == "C89":
326 self.__error("--slice-by not supported for C89")
327
328 if options.algorithm != None:
329 alg = options.algorithm.lower()
330 if alg in set(["bit-by-bit", "bbb", "all"]):
331 self.algorithm |= self.algo_bit_by_bit
332 if alg in set(["bit-by-bit-fast", "bbf", "all"]):
333 self.algorithm |= self.algo_bit_by_bit_fast
334 if alg in set(["table-driven", "tbl", "all"]):
335 self.algorithm |= self.algo_table_driven
336 if self.algorithm == 0:
337 self.__error("unknown algorithm {0:s}".format(options.algorithm))
338
339 if options.symbol_prefix != None:
340 self.symbol_prefix = options.symbol_prefix
341 if options.include_files != None:
342 self.include_files = options.include_files
343 if options.crc_type != None:
344 self.crc_type = options.crc_type
345 if options.output_file != None:
346 self.output_file = options.output_file
347 op_count = 0
348 if options.check_string != None:
349 self.action = self.action_check_str
350 self.check_string = options.check_string
351 op_count += 1
352 if options.check_hexstring != None:
353 self.action = self.action_check_hex_str
354 self.check_string = options.check_hexstring
355 op_count += 1
356 if options.check_file != None:
357 self.action = self.action_check_file
358 self.check_file = options.check_file
359 op_count += 1
360 if options.generate != None:
361 arg = options.generate.lower()
362 if arg == 'h':
363 self.action = self.action_generate_h
364 elif arg == 'c':
365 self.action = self.action_generate_c
366 elif arg == 'c-main':
367 self.action = self.action_generate_c_main
368 elif arg == 'table':
369 self.action = self.action_generate_table
370 else:
371 self.__error("don't know how to generate {0:s}".format(options.generate))
372 op_count += 1
373
374 if self.action == self.action_generate_table:
375 if self.algorithm & self.algo_table_driven == 0:
376 self.__error("the --generate table option is incompatible "
377 "with the --algorithm option")
378 self.algorithm = self.algo_table_driven
379 elif self.algorithm not in set(
380 [self.algo_bit_by_bit, self.algo_bit_by_bit_fast, self.algo_table_driven]):
381 self.__error("select an algorithm to be used in the generated file")
382 else:
383 if self.tbl_idx_width != 8:
384 self.__warning("reverting to Table Index Width = 8 "
385 "for internal CRC calculation")
386 self.tbl_idx_width = 8
387 self.tbl_width = 1 << options.table_idx_width
388 if op_count == 0:
389 self.action = self.action_check_str
390 if op_count > 1:
391 self.__error("too many actions specified")
392
393 if len(args) != 0:
394 self.__error("unrecognized argument(s): {0:s}".format(" ".join(args)))
395
396 def_params_acts = (self.action_check_str, self.action_check_hex_str,
397 self.action_check_file, self.action_generate_table)
398 if self.undefined_crc_parameters and self.action in set(def_params_acts):
399 self.__error("undefined parameters: Add {0:s} or use --model"
400 .format(", ".join(undefined_params)))
401 self.verbose = options.verbose
402
403
404
405 def __warning(self, message):
406 """
407 Print a warning message to stderr.
408 """
409 sys.stderr.write(
410 "{0:s}: warning: {1:s}\n".format(self.program_name, message))
411
412
413
414 def __error(self, message):
415 """
416 Print a error message to stderr and terminate the program.
417 """
418 sys.stderr.write(
419 "{0:s}: error: {1:s}\n".format(self.program_name, message))
420 sys.exit(1)
421
422
423def _model_cb(option, opt_str, value, parser):
424 """
425 This function sets up the single parameters if the 'model' option has been selected
426 by the user.
427 """
428 model_name = value.lower()
429 models = CrcModels()
430 model = models.get_params(model_name)
431 if model != None:
432 setattr(parser.values, 'width', model['width'])
433 setattr(parser.values, 'poly', model['poly'])
434 setattr(parser.values, 'reflect_in', model['reflect_in'])
435 setattr(parser.values, 'xor_in', model['xor_in'])
436 setattr(parser.values, 'reflect_out', model['reflect_out'])
437 setattr(parser.values, 'xor_out', model['xor_out'])
438 else:
439 models = CrcModels()
440 model_list = ", ".join(models.names())
441 raise OptionValueError(
442 "unsupported model {0:s}. Supported models are: {1:s}."
443 .format(value, model_list))
444
445
446def _check_hex(dummy_option, opt, value):
447 """
448 Checks if a value is given in a decimal integer of hexadecimal reppresentation.
449 Returns the converted value or rises an exception on error.
450 """
451 try:
452 if value.lower().startswith("0x"):
453 return int(value, 16)
454 else:
455 return int(value)
456 except ValueError:
457 raise OptionValueError(
458 "option {0:s}: invalid integer or hexadecimal value: {1:s}.".format(opt, value))
459
460
461def _check_bool(dummy_option, opt, value):
462 """
463 Checks if a value is given as a boolean value (either 0 or 1 or "true" or "false")
464 Returns the converted value or rises an exception on error.
465 """
466 if value.isdigit():
467 return int(value, 10) != 0
468 elif value.lower() == "false":
469 return False
470 elif value.lower() == "true":
471 return True
472 else:
473 raise OptionValueError("option {0:s}: invalid boolean value: {1:s}.".format(opt, value))
474
475
476class MyOption(Option):
477 """
478 New option parsing class extends the Option class
479 """
480 TYPES = Option.TYPES + ("hex", "bool")
481 TYPE_CHECKER = copy(Option.TYPE_CHECKER)
482 TYPE_CHECKER["hex"] = _check_hex
483 TYPE_CHECKER["bool"] = _check_bool
484