blob: 7868b2e60fe579cc86658745dfebd64dcb407605 [file] [log] [blame]
# pycrc -- parameterisable CRC calculation utility and C source code generator
#
# Copyright (c) 2006-2017 Thomas Pircher <tehpeh-web@tty1.net>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
# deal in the Software without restriction, including without limitation the
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
# sell copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.
"""
Option parsing library for pycrc.
use as follows:
from pycrc.opt import Options
opt = Options()
opt.parse(sys.argv[1:])
"""
from optparse import OptionParser, Option, OptionValueError
from copy import copy
import sys
from pycrc.models import CrcModels
class Options(object):
"""
The options parsing and validating class.
"""
# pylint: disable=too-many-instance-attributes, too-few-public-methods
# Bitmap of the algorithms
algo_none = 0x00
algo_bit_by_bit = 0x01
algo_bit_by_bit_fast = 0x02
algo_table_driven = 0x08
action_check_str = 0x01
action_check_hex_str = 0x02
action_check_file = 0x03
action_generate_h = 0x04
action_generate_c = 0x05
action_generate_c_main = 0x06
action_generate_table = 0x07
def __init__(self, progname='pycrc', version=None, url=None):
self.program_name = progname
self.version = version
self.version_str = "{0:s} v{1:s}".format(progname, version)
self.web_address = url
self.width = None
self.poly = None
self.reflect_in = None
self.xor_in = None
self.reflect_out = None
self.xor_out = None
self.tbl_idx_width = 8
self.tbl_width = 1 << self.tbl_idx_width
self.slice_by = 1
self.verbose = False
self.check_string = "123456789"
self.msb_mask = None
self.mask = None
self.algorithm = self.algo_none
self.symbol_prefix = "crc_"
self.crc_type = None
self.include_files = []
self.output_file = None
self.action = self.action_check_str
self.check_file = None
self.c_std = None
self.undefined_crc_parameters = False
def parse(self, argv=None):
"""
Parses and validates the options given as arguments
"""
# pylint: disable=too-many-branches, too-many-statements
usage = """python %prog [OPTIONS]
To calculate the checksum of a string or hexadecimal data:
python %prog [model] --check-string "123456789"
python %prog [model] --check-hexstring "313233343536373839"
To calculate the checksum of a file:
python %prog [model] --check-file filename
To generate the C source code and write it to filename:
python %prog [model] --generate c -o filename
The model can be defined either with the --model switch or by specifying each
of the following parameters:
--width --poly --reflect-in --xor-in --reflect-out --xor-out"""
models = CrcModels()
model_list = ", ".join(models.names())
parser = OptionParser(option_class=MyOption, usage=usage, version=self.version_str)
parser.add_option(
"-v", "--verbose",
action="store_true", dest="verbose", default=False,
help="be more verbose; print the value of the parameters "
"and the chosen model to stdout")
parser.add_option(
"--check-string",
action="store", type="string", dest="check_string",
help="calculate the checksum of a string (default: '123456789')",
metavar="STRING")
parser.add_option(
"--check-hexstring",
action="store", type="string", dest="check_hexstring",
help="calculate the checksum of a hexadecimal number string",
metavar="STRING")
parser.add_option(
"--check-file",
action="store", type="string", dest="check_file",
help="calculate the checksum of a file",
metavar="FILE")
parser.add_option(
"--generate",
action="store", type="string", dest="generate", default=None,
help="generate C source code; choose the type from {h, c, c-main, table}",
metavar="CODE")
parser.add_option(
"--std",
action="store", type="string", dest="c_std", default="C99",
help="choose the C dialect of the generated code from {C89, ANSI, C99}",
metavar="STD")
parser.add_option(
"--algorithm",
action="store", type="string", dest="algorithm", default="all",
help="choose an algorithm from "
"{bit-by-bit, bbb, bit-by-bit-fast, bbf, table-driven, tbl, all}",
metavar="ALGO")
parser.add_option(
"--model",
action="callback", callback=_model_cb, type="string", dest="model", default=None,
help="choose a parameter set from {{{0:s}}}".format(model_list),
metavar="MODEL")
parser.add_option(
"--width",
action="store", type="hex", dest="width",
help="use NUM bits in the polynomial",
metavar="NUM")
parser.add_option(
"--poly",
action="store", type="hex", dest="poly",
help="use HEX as polynomial",
metavar="HEX")
parser.add_option(
"--reflect-in",
action="store", type="bool", dest="reflect_in",
help="reflect the octets in the input message",
metavar="BOOL")
parser.add_option(
"--xor-in",
action="store", type="hex", dest="xor_in",
help="use HEX as initial value",
metavar="HEX")
parser.add_option(
"--reflect-out",
action="store", type="bool", dest="reflect_out",
help="reflect the resulting checksum before applying the --xor-out value",
metavar="BOOL")
parser.add_option(
"--xor-out",
action="store", type="hex", dest="xor_out",
help="xor the final CRC value with HEX",
metavar="HEX")
parser.add_option(
"--slice-by",
action="store", type="int", dest="slice_by",
help="read NUM bytes at a time from the input. NUM must be one of the values {4, 8, 16}",
metavar="NUM")
parser.add_option(
"--table-idx-width",
action="store", type="int", dest="table_idx_width",
help="use NUM bits to index the CRC table; NUM must be one of the values {1, 2, 4, 8}",
metavar="NUM")
parser.add_option(
"--force-poly",
action="store_true", dest="force_poly", default=False,
help="override any errors about possibly unsuitable polynoms")
parser.add_option(
"--symbol-prefix",
action="store", type="string", dest="symbol_prefix",
help="when generating source code, use STRING as prefix to the exported C symbols",
metavar="STRING")
parser.add_option(
"--crc-type",
action="store", type="string", dest="crc_type",
help="when generating source code, use STRING as crc_t type",
metavar="STRING")
parser.add_option(
"--include-file",
action="append", type="string", dest="include_files",
help="when generating source code, include also FILE as header file; "
"can be specified multiple times",
metavar="FILE")
parser.add_option(
"-o", "--output",
action="store", type="string", dest="output_file",
help="write the generated code to file instead to stdout",
metavar="FILE")
(options, args) = parser.parse_args(argv)
if options.c_std != None:
std = options.c_std.upper()
if std == "ANSI" or std == "C89":
self.c_std = "C89"
elif std == "C99":
self.c_std = std
else:
self.__error("unknown C standard {0:s}".format(options.c_std))
undefined_params = []
if options.width != None:
self.width = options.width
else:
undefined_params.append("--width")
if options.poly != None:
self.poly = options.poly
else:
undefined_params.append("--poly")
if options.reflect_in != None:
self.reflect_in = options.reflect_in
else:
undefined_params.append("--reflect-in")
if options.xor_in != None:
self.xor_in = options.xor_in
else:
undefined_params.append("--xor-in")
if options.reflect_out != None:
self.reflect_out = options.reflect_out
else:
undefined_params.append("--reflect-out")
if options.xor_out != None:
self.xor_out = options.xor_out
else:
undefined_params.append("--xor-out")
if options.table_idx_width != None:
if options.table_idx_width in set((1, 2, 4, 8)):
self.tbl_idx_width = options.table_idx_width
self.tbl_width = 1 << options.table_idx_width
else:
self.__error("unsupported table-idx-width {0:d}".format(options.table_idx_width))
if self.poly != None and self.poly % 2 == 0 and not options.force_poly:
self.__error("even polinomials are not allowed by default. Use --force-poly to override this.")
if self.width != None:
if self.width <= 0:
self.__error("Width must be strictly positive")
self.msb_mask = 0x1 << (self.width - 1)
self.mask = ((self.msb_mask - 1) << 1) | 1
if self.poly != None and self.poly >> (self.width + 1) != 0 and not options.force_poly:
self.__error("the polynomial is wider than the supplied Width. Use --force-poly to override this.")
if self.poly != None:
self.poly = self.poly & self.mask
if self.xor_in != None:
self.xor_in = self.xor_in & self.mask
if self.xor_out != None:
self.xor_out = self.xor_out & self.mask
else:
self.msb_mask = None
self.mask = None
if self.width == None or \
self.poly == None or \
self.reflect_in == None or \
self.xor_in == None or \
self.reflect_out == None or \
self.xor_out == None:
self.undefined_crc_parameters = True
else:
self.undefined_crc_parameters = False
if options.slice_by != None:
if options.slice_by in set((4, 8, 16)):
self.slice_by = options.slice_by
else:
self.__error("unsupported slice-by {0:d}".format(options.slice_by))
if self.undefined_crc_parameters:
self.__error("slice-by is only implemented for fully defined models")
if self.tbl_idx_width != 8:
self.__error("slice-by is only implemented for table-idx-width=8")
# FIXME tp: Fix corner cases and disable the following tests
if self.width < 8:
self.__warning("disabling slice-by for width {0}".format(self.width))
self.slice_by = 1
if self.width < 16:
self.__warning("disabling slice-by for width {0}".format(self.width))
self.slice_by = 1
if self.width > 32:
self.__warning("disabling slice-by for width {0}".format(self.width))
self.slice_by = 1
if not self.reflect_in:
self.__warning("disabling slice-by for non-reflected algorithm")
self.slice_by = 1
# FIXME tp: reintroduce this?
# if self.width % 8 != 0:
# self.__error("slice-by is only implemented for width multiples of 8")
# if options.slice_by < self.width / 8:
# self.__error("slice-by must be greater or equal width / 8")
if self.c_std == "C89":
self.__error("--slice-by not supported for C89")
if options.algorithm != None:
alg = options.algorithm.lower()
if alg in set(["bit-by-bit", "bbb", "all"]):
self.algorithm |= self.algo_bit_by_bit
if alg in set(["bit-by-bit-fast", "bbf", "all"]):
self.algorithm |= self.algo_bit_by_bit_fast
if alg in set(["table-driven", "tbl", "all"]):
self.algorithm |= self.algo_table_driven
if self.algorithm == 0:
self.__error("unknown algorithm {0:s}".format(options.algorithm))
if options.symbol_prefix != None:
self.symbol_prefix = options.symbol_prefix
if options.include_files != None:
self.include_files = options.include_files
if options.crc_type != None:
self.crc_type = options.crc_type
if options.output_file != None:
self.output_file = options.output_file
op_count = 0
if options.check_string != None:
self.action = self.action_check_str
self.check_string = options.check_string
op_count += 1
if options.check_hexstring != None:
self.action = self.action_check_hex_str
self.check_string = options.check_hexstring
op_count += 1
if options.check_file != None:
self.action = self.action_check_file
self.check_file = options.check_file
op_count += 1
if options.generate != None:
arg = options.generate.lower()
if arg == 'h':
self.action = self.action_generate_h
elif arg == 'c':
self.action = self.action_generate_c
elif arg == 'c-main':
self.action = self.action_generate_c_main
elif arg == 'table':
self.action = self.action_generate_table
else:
self.__error("don't know how to generate {0:s}".format(options.generate))
op_count += 1
if self.action == self.action_generate_table:
if self.algorithm & self.algo_table_driven == 0:
self.__error("the --generate table option is incompatible "
"with the --algorithm option")
self.algorithm = self.algo_table_driven
elif self.algorithm not in set(
[self.algo_bit_by_bit, self.algo_bit_by_bit_fast, self.algo_table_driven]):
self.__error("select an algorithm to be used in the generated file")
else:
if self.tbl_idx_width != 8:
self.__warning("reverting to Table Index Width = 8 "
"for internal CRC calculation")
self.tbl_idx_width = 8
self.tbl_width = 1 << options.table_idx_width
if op_count == 0:
self.action = self.action_check_str
if op_count > 1:
self.__error("too many actions specified")
if len(args) != 0:
self.__error("unrecognized argument(s): {0:s}".format(" ".join(args)))
def_params_acts = (self.action_check_str, self.action_check_hex_str,
self.action_check_file, self.action_generate_table)
if self.undefined_crc_parameters and self.action in set(def_params_acts):
self.__error("undefined parameters: Add {0:s} or use --model"
.format(", ".join(undefined_params)))
self.verbose = options.verbose
def __warning(self, message):
"""
Print a warning message to stderr.
"""
sys.stderr.write(
"{0:s}: warning: {1:s}\n".format(self.program_name, message))
def __error(self, message):
"""
Print a error message to stderr and terminate the program.
"""
sys.stderr.write(
"{0:s}: error: {1:s}\n".format(self.program_name, message))
sys.exit(1)
def _model_cb(option, opt_str, value, parser):
"""
This function sets up the single parameters if the 'model' option has been selected
by the user.
"""
model_name = value.lower()
models = CrcModels()
model = models.get_params(model_name)
if model != None:
setattr(parser.values, 'width', model['width'])
setattr(parser.values, 'poly', model['poly'])
setattr(parser.values, 'reflect_in', model['reflect_in'])
setattr(parser.values, 'xor_in', model['xor_in'])
setattr(parser.values, 'reflect_out', model['reflect_out'])
setattr(parser.values, 'xor_out', model['xor_out'])
else:
models = CrcModels()
model_list = ", ".join(models.names())
raise OptionValueError(
"unsupported model {0:s}. Supported models are: {1:s}."
.format(value, model_list))
def _check_hex(dummy_option, opt, value):
"""
Checks if a value is given in a decimal integer of hexadecimal reppresentation.
Returns the converted value or rises an exception on error.
"""
try:
if value.lower().startswith("0x"):
return int(value, 16)
else:
return int(value)
except ValueError:
raise OptionValueError(
"option {0:s}: invalid integer or hexadecimal value: {1:s}.".format(opt, value))
def _check_bool(dummy_option, opt, value):
"""
Checks if a value is given as a boolean value (either 0 or 1 or "true" or "false")
Returns the converted value or rises an exception on error.
"""
if value.isdigit():
return int(value, 10) != 0
elif value.lower() == "false":
return False
elif value.lower() == "true":
return True
else:
raise OptionValueError("option {0:s}: invalid boolean value: {1:s}.".format(opt, value))
class MyOption(Option):
"""
New option parsing class extends the Option class
"""
TYPES = Option.TYPES + ("hex", "bool")
TYPE_CHECKER = copy(Option.TYPE_CHECKER)
TYPE_CHECKER["hex"] = _check_hex
TYPE_CHECKER["bool"] = _check_bool