Brian Silverman | 84b2223 | 2019-01-25 20:29:29 -0800 | [diff] [blame^] | 1 | # 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 | """ |
| 25 | Option parsing library for pycrc. |
| 26 | use as follows: |
| 27 | |
| 28 | from pycrc.opt import Options |
| 29 | |
| 30 | opt = Options() |
| 31 | opt.parse(sys.argv[1:]) |
| 32 | """ |
| 33 | |
| 34 | from optparse import OptionParser, Option, OptionValueError |
| 35 | from copy import copy |
| 36 | import sys |
| 37 | from pycrc.models import CrcModels |
| 38 | |
| 39 | |
| 40 | class 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 | |
| 100 | To calculate the checksum of a string or hexadecimal data: |
| 101 | python %prog [model] --check-string "123456789" |
| 102 | python %prog [model] --check-hexstring "313233343536373839" |
| 103 | |
| 104 | To calculate the checksum of a file: |
| 105 | python %prog [model] --check-file filename |
| 106 | |
| 107 | To generate the C source code and write it to filename: |
| 108 | python %prog [model] --generate c -o filename |
| 109 | |
| 110 | The model can be defined either with the --model switch or by specifying each |
| 111 | of 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 | |
| 423 | def _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 | |
| 446 | def _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 | |
| 461 | def _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 | |
| 476 | class 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 | |