diff --git a/data_models/protocols/pppoe.py b/data_models/protocols/pppoe.py index 74881ca..c1a40a4 100644 --- a/data_models/protocols/pppoe.py +++ b/data_models/protocols/pppoe.py @@ -36,13 +36,22 @@ def build_data_model(self): # refer to RFC 2516 + def cycle_tags(tag): + tag.freeze() + if tag['.*/type'].get_current_raw_val() == 0x102: + tag['.*/type'].unfreeze() + tag.freeze() + tag['.*/type'].unfreeze() + tag.unfreeze(reevaluate_constraints=True) + return tag + tag_desc = \ {'name': 'tag', + 'evolution_func': cycle_tags, 'contents': [ {'name': 'type', - 'random': True, - 'contents': UINT16_be(values=[0,0x0101,0x0102,0x0103,0x0104,0x0105, - 0x0110,0x201,0x0202,0x0203]), + 'contents': UINT16_be(values=[0x0101,0x0102,0x0103,0x0104,0x0105, + 0x0110,0x201,0x0202,0x0203,0]), 'absorb_csts': AbsFullCsts()}, {'name': 'len', 'contents': UINT16_be(), @@ -116,7 +125,6 @@ def build_data_model(self): mh = ModelHelper(delayed_jobs=True, add_env=False) tag_node = mh.create_graph_from_desc(tag_desc) - tag_node_4pads = tag_node.get_clone() tag_service_name = tag_node.get_clone('tag_sn') tag_service_name['.*/type'].set_values(value_type=UINT16_be(values=[0x0101])) @@ -124,12 +132,17 @@ def build_data_model(self): tag_host_uniq = tag_node.get_clone('tag_host_uniq') tag_host_uniq['.*/type'].set_values(value_type=UINT16_be(values=[0x0103])) + tag_host_uniq_pads = tag_host_uniq.get_clone() + tag_ac_name = tag_node.get_clone('tag_ac_name') # Access Concentrator Name tag_ac_name['.*/type'].set_values(value_type=UINT16_be(values=[0x0102])) tag_sn_error = tag_node.get_clone('tag_sn_error') # Service Name Error tag_sn_error['.*/type'].set_values(value_type=UINT16_be(values=[0x0202])) + tag_service_name_pads = tag_service_name.get_clone() + tag_node_pads = tag_node.get_clone() + pppoe_desc = \ {'name': 'pppoe', 'contents': [ @@ -168,19 +181,28 @@ def build_data_model(self): 'custo_clear': MH.Custo.NTerm.FrozenCopy, 'exists_if': (IntCondition(0x9), 'code'), 'contents': [ - (tag_service_name, 1), - (tag_node, 0, 4) + (tag_service_name.get_clone(), 1), + (tag_node.get_clone(), 0, 4) ]}, {'name': '4pado', 'shape_type': MH.FullyRandom, 'custo_clear': MH.Custo.NTerm.FrozenCopy, 'exists_if': (IntCondition(0x7), 'code'), 'contents': [ - (tag_ac_name, 1), + (tag_host_uniq.get_clone(), 1), + (tag_ac_name.get_clone(), 1), (tag_service_name.get_clone(), 1), - {'name': 'host_uniq_stub', - 'contents': String(values=[''])}, - (tag_node.get_clone(), 0, 4) + ], + 'alt': [ + {'conf': 'fuzz', + 'shape_type': MH.Ordered, + 'exists_if': (IntCondition(0x7), 'code'), + 'contents': [ + (tag_node.get_clone(), 0, 8), + (tag_host_uniq.get_clone(), 1), + (tag_ac_name.get_clone(), 1), + (tag_service_name.get_clone(), 1), + ]} ]}, {'name': '4padr', 'shape_type': MH.FullyRandom, @@ -198,27 +220,34 @@ def build_data_model(self): # Accept PPPoE session Case {'weight': 10, 'contents': [ - (tag_service_name.get_clone(), 1), - {'name': ('host_uniq_stub', 2), - 'contents': String(values=[''])}, - (tag_node_4pads, 0, 4) + (tag_service_name_pads, 1), + (tag_host_uniq_pads, 1), + (tag_node_pads, 0, 4) ]}, # Reject PPPoE session Case {'weight': 2, 'contents': [ (tag_sn_error, 1), - {'name': ('host_uniq_stub', 2)}, - (tag_node_4pads, 0, 4) + (tag_host_uniq_pads, 1), + (tag_node_pads, 0, 4) ]}, + ], + 'alt': [ + {'conf': 'fuzz', + 'exists_if': (IntCondition(0x65), 'code'), + 'contents': [ + (tag_node.get_clone(), 0, 8), + (tag_service_name_pads.get_clone(), 1), + (tag_host_uniq_pads.get_clone(), 1) + ]} ]}, {'name': '4padt', 'shape_type': MH.FullyRandom, 'custo_clear': MH.Custo.NTerm.FrozenCopy, 'exists_if': (IntCondition(0xa7), 'code'), 'contents': [ - {'name': ('host_uniq_stub', 3), - 'contents': String(values=[''])}, - (tag_node.get_clone(), 0, 4) + {'contents': tag_host_uniq.get_clone()}, + {'contents': tag_node.get_clone(), 'qty': (0, 4)} ]} ]}, {'name': 'padding', diff --git a/data_models/protocols/pppoe_strategy.py b/data_models/protocols/pppoe_strategy.py index 84e11e1..9e67c91 100644 --- a/data_models/protocols/pppoe_strategy.py +++ b/data_models/protocols/pppoe_strategy.py @@ -24,6 +24,8 @@ from framework.tactics_helpers import * from framework.scenario import * from framework.global_resources import * +from framework.data_model_helpers import MH +from framework.target import * tactics = Tactics() @@ -60,6 +62,7 @@ def retrieve_X_from_feedback(env, current_step, next_step, feedback, x='padi', u off = data.find(mac_dst) data = data[off:] result = msg_x.absorb(data, constraints=AbsNoCsts(size=True, struct=True)) + print('\n [ ABS result: {!s} ]'.format(result)) if result[0] == AbsorbStatus.FullyAbsorbed: try: service_name = msg_x['.*/value/v101'].to_bytes() @@ -76,17 +79,13 @@ def retrieve_X_from_feedback(env, current_step, next_step, feedback, x='padi', u host_uniq = host_uniq.to_bytes() env.host_uniq = host_uniq t_fix_pppoe_msg_fields.host_uniq = host_uniq - elif update and hasattr(env, 'host_uniq'): - host_uniq = env.host_uniq - else: - pass if update: # we update the seed of the data process next_step.node.freeze() try: next_step.node['.*/tag_sn/value/v101'] = service_name next_step.node['.*/tag_sn$'].unfreeze(recursive=True, reevaluate_constraints=True) - next_step.node.freeze() + next_step.node['.*/tag_sn$'].freeze() except: pass @@ -145,38 +144,42 @@ def disrupt_data(self, dm, target, prev_data): print("\n*** 'service_name' not found in the environment! ***") if self.host_uniq: - new_tag = dm.get_data('tag_host_uniq') - new_tag['.*/v103'] = self.host_uniq - new_tag.unfreeze(recursive=True, reevaluate_constraints=True) - new_tag.freeze() try: - n['.*/host_uniq_stub'].set_contents(new_tag) - prev_data.add_info("update 'host_uniq'") + if not n['.*/tag_host_uniq/.*/v103'].is_attr_set(MH.Attr.LOCKED) and \ + not n['.*/tag_host_uniq/len'].is_attr_set(MH.Attr.LOCKED) and \ + not n['.*/tag_host_uniq/type'].is_attr_set(MH.Attr.LOCKED): + n['.*/tag_host_uniq/.*/v103'] = self.host_uniq + tag_uniq = n['.*/tag_host_uniq$'] + tag_uniq.unfreeze(recursive=True, reevaluate_constraints=True) + tag_uniq.freeze() + prev_data.add_info("update 'host_uniq' with: {!s}".format(self.host_uniq)) + else: + print("\n*** 'tag_host_uniq' is currently fuzzed. ignore its update ***") except: - print(error_msg.format('host_uniq_stub')) + print(error_msg.format('tag_host_uniq')) else: - print("\n*** 'host_uniq_stub' not found in the environment! ***") + print("\n*** 'tag_host_uniq' not found in the environment! ***") if self.reevaluate_csts: n.unfreeze(recursive=True, reevaluate_constraints=True) - else: - try: - n['.*/length$'].unfreeze() - except: - print(error_msg.format('length')) + n.freeze() - n.show() + # n.show() return prev_data ### PADI fuzz scenario ### -step_wait_padi = NoDataStep(fbk_timeout=1) - -dp_pado = DataProcess(process=[('tTYPE', UI(init=1), UI(order=True)), 'FIX_FIELDS'], seed='pado') -dp_pado.append_new_process([('tSTRUCT', UI(init=1), UI(deep=True)), 'FIX_FIELDS']) -step_send_pado = Step(dp_pado) +step_wait_padi = NoDataStep(fbk_timeout=10, fbk_mode=Target.FBK_WAIT_UNTIL_RECV) + +dp_pado = DataProcess(process=[('ALT', None, UI(conf='fuzz')), + ('tTYPE', UI(init=20), UI(order=True, fuzz_mag=0.7)), + 'FIX_FIELDS#pado1'], seed='pado') +dp_pado.append_new_process([('ALT', None, UI(conf='fuzz')), + ('tSTRUCT', UI(init=1), UI(deep=True)), 'FIX_FIELDS#pado2']) +step_send_pado = Step(dp_pado, fbk_timeout=0.1, fbk_mode=Target.FBK_WAIT_FULL_TIME) # step_send_pado = Step('pado') -step_end = Step('padt') +step_end = Step(DataProcess(process=[('FIX_FIELDS#pado3', None, UI(reevaluate_csts=True))], + seed='padt'), fbk_timeout=0.1, fbk_mode=Target.FBK_WAIT_FULL_TIME) step_wait_padi.connect_to(step_send_pado, cbk_after_fbk=retrieve_padi_from_feedback_and_update) step_send_pado.connect_to(step_end) @@ -186,16 +189,19 @@ def disrupt_data(self, dm, target, prev_data): sc1.set_anchor(step_wait_padi) ### PADS fuzz scenario ### -step_wait_padi = NoDataStep(fbk_timeout=1) -step_send_valid_pado = Step(DataProcess(process=[('FIX_FIELDS#2', None, UI(reevaluate_csts=True))], - seed='pado')) -step_send_padt = Step(DataProcess(process=[('FIX_FIELDS#3', None, UI(reevaluate_csts=True))], - seed='padt'), fbk_timeout=0.1) - -dp_pads = DataProcess(process=[('tTYPE#2', UI(init=1), UI(order=True)), 'FIX_FIELDS'], seed='pads') -dp_pads.append_new_process([('tSTRUCT#2', UI(init=1), UI(deep=True)), 'FIX_FIELDS']) -step_send_fuzzed_pads = Step(dp_pads) -step_wait_padr = NoDataStep(fbk_timeout=1) +step_wait_padi = NoDataStep(fbk_timeout=10, fbk_mode=Target.FBK_WAIT_UNTIL_RECV) +step_send_valid_pado = Step(DataProcess(process=[('FIX_FIELDS#pads1', None, UI(reevaluate_csts=True))], + seed='pado'), fbk_timeout=0.1, fbk_mode=Target.FBK_WAIT_FULL_TIME) +step_send_padt = Step(DataProcess(process=[('FIX_FIELDS#pads2', None, UI(reevaluate_csts=True))], + seed='padt'), fbk_timeout=0.1, fbk_mode=Target.FBK_WAIT_FULL_TIME) + +dp_pads = DataProcess(process=[('ALT', None, UI(conf='fuzz')), + ('tTYPE#2', UI(init=1), UI(order=True, fuzz_mag=0.7)), + 'FIX_FIELDS#pads3'], seed='pads') +dp_pads.append_new_process([('ALT', None, UI(conf='fuzz')), + ('tSTRUCT#2', UI(init=1), UI(deep=True)), 'FIX_FIELDS#pads4']) +step_send_fuzzed_pads = Step(dp_pads, fbk_timeout=0.1, fbk_mode=Target.FBK_WAIT_FULL_TIME) +step_wait_padr = NoDataStep(fbk_timeout=10, fbk_mode=Target.FBK_WAIT_UNTIL_RECV) step_wait_padi.connect_to(step_send_valid_pado, cbk_after_fbk=retrieve_padi_from_feedback) step_send_valid_pado.connect_to(step_send_fuzzed_pads, cbk_after_fbk=retrieve_padr_from_feedback_and_update) diff --git a/data_models/tuto.py b/data_models/tuto.py index 7a3aeac..c49a16f 100644 --- a/data_models/tuto.py +++ b/data_models/tuto.py @@ -431,7 +431,7 @@ def keycode_helper(blob, constraints, node_internals): ]} regex_desc = {'name': 'regex', - 'contents': '(333|444)|(foo|bar)|[\d]|[th|is]'} + 'contents': '(333|444)|(foo|bar)|\d|[th|is]'} self.register(test_node_desc, abstest_desc, abstest2_desc, separator_desc, diff --git a/docs/source/conf.py b/docs/source/conf.py index 453a636..f8198bb 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -57,7 +57,7 @@ # The short X.Y version. version = '0.25' # The full version, including alpha/beta/rc tags. -release = '0.25.0' +release = '0.25.1' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/docs/source/data_model.rst b/docs/source/data_model.rst index c6b5364..41f7ba2 100644 --- a/docs/source/data_model.rst +++ b/docs/source/data_model.rst @@ -461,6 +461,16 @@ conf Used within the scope of the description of an alternative configuration. It set the name of the alternative configuration. +evolution_func + This attribute allows to provide a function that will be used in the case the described node is + instantiated more than once by a containing non-terminal node further to a + :meth:`framework.data_model.Node.freeze` operation (refer to the ``qty`` keyword). + The function will be called on every node instance (but the first one) before this node + incorporate the frozen form of the non-terminal. Besides, the node returned by the function will + be used as the base node for the next instantiation (which makes node evolution easier). + The function shall have the following signature:: + + func_name( Node ) --> Node custo_set, custo_clear These attributes are used to customize the behavior of the described node. diff --git a/docs/source/disruptors.rst b/docs/source/disruptors.rst index 7ec1317..7a1ea2c 100644 --- a/docs/source/disruptors.rst +++ b/docs/source/disruptors.rst @@ -243,17 +243,18 @@ Parameters: | | desc: graph path regexp to select nodes on which the disruptor should | | apply | | default: None [type: str] - |_ singleton - | | desc: consume also terminal nodes with only one possible value - | | default: False [type: bool] - |_ nt_only - | | desc: walk through non-terminal nodes only - | | default: False [type: bool] + |_ order + | | desc: when set to True, the walking order is strictly guided by the + | | data structure. Otherwise, fuzz weight (if specified in the + | | data model) is used for ordering + | | default: True [type: bool] |_ fix_all | | desc: for each produced data, reevaluate the constraints on the whole | | graph | | default: True [type: bool] - + |_ nt_only + | | desc: walk through non-terminal nodes only + | | default: False [type: bool] Stateless Disruptors ==================== diff --git a/docs/source/probes.rst b/docs/source/probes.rst index 90d1703..aa6f3f8 100644 --- a/docs/source/probes.rst +++ b/docs/source/probes.rst @@ -65,6 +65,15 @@ Description: This generic backend enables you to interact with a monitored system through an serial line. +Shell_Backend +------------- + +Reference: + :class:`framework.monitor.Shell_Backend` + +Description: + This generic backend enables you to interact with a local monitored system + through a shell. Generic Probes ============== diff --git a/docs/source/scenario.rst b/docs/source/scenario.rst index 9587aff..5233750 100644 --- a/docs/source/scenario.rst +++ b/docs/source/scenario.rst @@ -72,7 +72,8 @@ From line 9 to 11 we define 3 :class:`framework.scenario.Step`: time duration that ``Fuddly`` should respect for collecting the feedback from the target (feedback timeout). This timeout is actually handled by the ``Target`` object, which may decide to respect it or not. For instance the ``NetworkTarget`` respect it while the ``EmptyTarget`` (default target) - do not. + do not. Note that the feedback mode (refer to :ref:`targets`) is also supported and can be set + through the parameter ``fbk_mode``. - The second step commands the framework to send a data of type ``separator`` and change the feedback timeout to 5. diff --git a/docs/source/targets.rst b/docs/source/targets.rst index 3d620e0..fd5cc34 100644 --- a/docs/source/targets.rst +++ b/docs/source/targets.rst @@ -14,6 +14,20 @@ Some of them will automatically provide feedback if an error occurs, to make ``fuddly`` aware of it and act accordingly (refer to :ref:`tuto:probes` for more information on that topic). +Additionally, if the generic target support feedback retrieval, the way it +is retrieved is guided by a feedback timeout and one of the following mode: + +- :const:`framework.target.Target.FBK_WAIT_FULL_TIME`: Wait for the full + time slot allocated for feedback retrieval +- :const:`framework.target.Target.FBK_WAIT_UNTIL_RECV`: Wait until the + target has sent something back to us + +The feedback timeout is set through :meth:`framework.target.Target.set_feedback_timeout`, +while the modes are set through :meth:`framework.target.Target.set_feedback_mode`. + +.. note:: + Depending on the generic target, all the feedback modes are not supported. + NetworkTarget ============= @@ -48,6 +62,11 @@ Feedback: encountered while delivering data to the target. +Supported Feedback Mode: + - :const:`framework.target.Target.FBK_WAIT_FULL_TIME` + - :const:`framework.target.Target.FBK_WAIT_UNTIL_RECV` + + Usage Example: .. code-block:: python :linenos: @@ -98,7 +117,8 @@ Usage Example: We set some time constraints: ``fbk_timeout`` for gathering feedback from all the interfaces; ``sending_delay`` for sending data to the target (client mode) or waiting for client connections before - sending data to them (server mode). + sending data to them (server mode). Note this method is specific to + this target and remains consistent with :meth:`framework.target.Target.set_feedback_timeout`. @@ -124,6 +144,11 @@ Feedback: This target will automatically provide feedback if the application writes on ``stderr`` or returns a negative status or terminates/crashes. + +Supported Feedback Mode: + - :const:`framework.target.Target.FBK_WAIT_UNTIL_RECV` + + Usage example: .. code-block:: python :linenos: @@ -206,6 +231,9 @@ Feedback: This target will automatically provide feedback if an error is received through the serial line used to interact with the SIM card. +Supported Feedback Mode: + - :const:`framework.target.Target.FBK_WAIT_FULL_TIME` + Usage Example: .. code-block:: python :linenos: diff --git a/framework/data_model.py b/framework/data_model.py index af70999..5844fc8 100644 --- a/framework/data_model.py +++ b/framework/data_model.py @@ -33,6 +33,7 @@ import binascii import collections import traceback +import uuid from enum import Enum @@ -62,6 +63,7 @@ def __init__(self, data=None): self._blocked = False self.feedback_timeout = None + self.feedback_mode = None self.info_list = [] self.info = {} @@ -616,7 +618,9 @@ def _handle_cond(self, val): def check(self, node): node_val = node._tobytes() - # node_val = node_val.replace(Node.DEFAULT_DISABLED_VALUE, b'') + if Node.DEFAULT_DISABLED_VALUE: + node_val = node_val.replace(Node.DEFAULT_DISABLED_VALUE, b'') + if self.positive_mode: if isinstance(self.val, (tuple, list)): result = node_val in self.val @@ -648,7 +652,10 @@ def __init__(self, val=None, neg_val=None): self.val = neg_val def check(self, node): - assert(node.is_typed_value(subkind=fvt.INT)) + if node.is_genfunc(): + node = node.generated_node + + assert node.is_typed_value(subkind=fvt.INT) if isinstance(self.val, (tuple, list)): if self.positive_mode: @@ -708,6 +715,9 @@ def __init__(self, sf, val=None, neg_val=None): def check(self, node): + if node.is_genfunc(): + node = node.generated_node + assert(node.is_typed_value(subkind=fvt.BitField)) for sf, val, neg_val in zip(self.sf, self.val, self.neg_val): @@ -732,7 +742,8 @@ class NodeCustomization(object): """ _custo_items = {} - def __init__(self, items_to_set=None, items_to_clear=None): + def __init__(self, items_to_set=None, items_to_clear=None, transform_func=None): + self._transform_func = transform_func self._custo_items = copy.copy(self._custo_items) if items_to_set is not None: if isinstance(items_to_set, int): @@ -757,6 +768,10 @@ def __getitem__(self, key): else: return None + @property + def transform_func(self): + return self._transform_func + def __copy__(self): new_custo = type(self)() new_custo.__dict__.update(self.__dict__) @@ -860,10 +875,10 @@ class NodeInternals(object): Separator = 15 DEBUG = 30 + LOCKED = 50 DISABLED = 100 - default_custo = None def __init__(self, arg=None): @@ -884,6 +899,10 @@ def __init__(self, arg=None): NodeInternals.Separator: False, # Used for debugging purpose NodeInternals.DEBUG: False, + # Used to express that someone (a disruptor for instance) is + # currently doing something with the node and doesn't want + # that someone else modify it. + NodeInternals.LOCKED: False, ### INTERNAL USAGE ### NodeInternals.DISABLED: False } @@ -1458,10 +1477,10 @@ def _get_value(self, conf=None, recursive=True, return_node_internals=False): if return_node_internals: return (Node.DEFAULT_DISABLED_NODEINT, True) else: - return (b'', True) + return (Node.DEFAULT_DISABLED_VALUE, True) def get_raw_value(self, **kwargs): - return b'' + return Node.DEFAULT_DISABLED_VALUE def set_child_env(self, env): print('Empty:', hex(id(self))) @@ -3044,6 +3063,7 @@ def _sync_size_handling(node): new_node = None + transformed_node = None for i in range(nb): # 'unique' mode if mode == 'u': @@ -3055,7 +3075,8 @@ def _sync_size_handling(node): # if self.is_attr_set(NodeInternals.Determinist): ignore_fstate = not self.custo.frozen_copy_mode - new_node = Node(nid, base_node=base_node, ignore_frozen_state=ignore_fstate, + node_to_copy = base_node if transformed_node is None else transformed_node + new_node = Node(nid, base_node=node_to_copy, ignore_frozen_state=ignore_fstate, accept_external_entanglement=True, acceptance_set=set(external_entangled_nodes + subnode_list)) new_node._reset_depth(parent_depth=base_node.depth-1) @@ -3067,6 +3088,19 @@ def _sync_size_handling(node): else: pass + if base_node.custo and base_node.custo.transform_func is not None: + try: + transformed_node = base_node.custo.transform_func(new_node) + except: + print('\n*** ERROR: User-provided NodeCustomization.transform_func()' + ' raised an exception. We ignore it.') + else: + if isinstance(new_node, Node): + new_node = transformed_node + else: + print('\n*** ERROR: User-provided NodeCustomization.transform_func()' + ' should return a Node. Thus we ignore its production.') + new_node._set_clone_info((base_node.tmp_ref_count-1, nb), base_node) _sync_size_handling(new_node) @@ -4831,7 +4865,7 @@ class Node(object): DJOBS_PRIO_dynhelpers = 200 DJOBS_PRIO_genfunc = 300 - DEFAULT_DISABLED_VALUE = b'' #b'**TO_REMOVE**' + DEFAULT_DISABLED_VALUE = b'' #b'' DEFAULT_DISABLED_NODEINT = NodeInternals_Empty() CORRUPT_EXIST_COND = 5 @@ -5831,7 +5865,8 @@ def freeze(self, conf=None, recursive=True, return_node_internals=False): ret = self._get_value(conf=conf, recursive=recursive, return_node_internals=return_node_internals) - if self.env is not None and self.env.delayed_jobs_enabled and not self._delayed_jobs_called: + if self.env is not None and self.env.delayed_jobs_enabled and \ + (not self._delayed_jobs_called or self.env.delayed_jobs_pending): self._delayed_jobs_called = True if self.env.djobs_exists(Node.DJOBS_PRIO_nterm_existence): @@ -6410,6 +6445,10 @@ def __init__(self): self._reentrancy_cpt = 0 # self.cpt = 0 + @property + def delayed_jobs_pending(self): + return bool(self._sorted_jobs) + def __getattr__(self, name): if hasattr(self.env4NT, name): return self.env4NT.__getattribute__(name) @@ -6623,7 +6662,7 @@ def __copy__(self): # new_env._sorted_jobs = copy.copy(self._sorted_jobs) # new_env._djob_keys = copy.copy(self._djob_keys) # new_env._djob_groups = copy.copy(self._djob_groups) - new_env.id_list = copy.copy(self.id_list) + # new_env.id_list = copy.copy(self.id_list) # new_env.cpt = 0 return new_env diff --git a/framework/data_model_helpers.py b/framework/data_model_helpers.py index 7ecaa60..383f66e 100644 --- a/framework/data_model_helpers.py +++ b/framework/data_model_helpers.py @@ -138,6 +138,7 @@ class Attr: Separator = NodeInternals.Separator + LOCKED = NodeInternals.LOCKED DEBUG = NodeInternals.DEBUG ########################### @@ -599,7 +600,7 @@ class ModelHelper(object): valid_keys = [ # generic description keys 'name', 'contents', 'qty', 'clone', 'type', 'alt', 'conf', - 'custo_set', 'custo_clear', + 'custo_set', 'custo_clear', 'evolution_func', # NonTerminal description keys 'weight', 'shape_type', 'section_type', 'duplicate_mode', 'weights', 'separator', 'prefix', 'suffix', 'unique', @@ -1018,6 +1019,7 @@ def _create_leaf_node(self, desc, node=None): def _handle_custo(self, node, desc, conf): custo_set = desc.get('custo_set', None) custo_clear = desc.get('custo_clear', None) + transform_func = desc.get('evolution_func', None) if node.is_genfunc(conf=conf): Custo = GenFuncCusto @@ -1049,8 +1051,9 @@ def _handle_custo(self, node, desc, conf): else: return - if custo_set or custo_clear: - custo = Custo(items_to_set=custo_set, items_to_clear=custo_clear) + if custo_set or custo_clear or transform_func: + custo = Custo(items_to_set=custo_set, items_to_clear=custo_clear, + transform_func=transform_func) internals = node.conf(conf) internals.customize(custo) @@ -1808,11 +1811,12 @@ def advance(self, ctx): class EscapeMetaSequence(Group): def _run(self, ctx): - if ctx.choice and len(ctx.values) > 1 and len(ctx.buffer) > 1: - raise InconvertibilityError() if ctx.buffer is not None: + if ctx.choice and len(ctx.values) > 1 and len(ctx.buffer) > 1: + raise InconvertibilityError() + if len(ctx.buffer) == 0: if len(ctx.values[:-1]) > 0: diff --git a/framework/database.py b/framework/database.py index f5ab814..114f65d 100644 --- a/framework/database.py +++ b/framework/database.py @@ -597,13 +597,13 @@ def handle_dmaker(dmk_pattern, info, dmk_type, dmk_name, name_sep_sz, id_src=Non if with_fbk: for src, tstamp, status, content in feedback: - msg += colorize("\n Status(", rgb=Color.FMKINFOGROUP) + \ - colorize("{:s}".format(src), rgb=Color.FMKSUBINFO) + \ - colorize(" | ", rgb=Color.FMKINFOGROUP) + \ - colorize("{:s}".format(tstamp.strftime("%d/%m/%Y - %H:%M:%S")), - rgb=Color.FMKSUBINFO) + \ - colorize(")", rgb=Color.FMKINFOGROUP) + \ - colorize(" = {!s}".format(status), rgb=Color.FMKSUBINFO) + msg += colorize("\n Status(", rgb=Color.FMKINFOGROUP) + msg += colorize("{!s}".format(src), rgb=Color.FMKSUBINFO) + msg += colorize(" | ", rgb=Color.FMKINFOGROUP) + msg += colorize("{:s}".format(tstamp.strftime("%d/%m/%Y - %H:%M:%S")), + rgb=Color.FMKSUBINFO) + msg += colorize(")", rgb=Color.FMKINFOGROUP) + msg += colorize(" = {!s}".format(status), rgb=Color.FMKSUBINFO) if content: content = gr.unconvert_from_internal_repr(content) if sys.version_info[0] > 2: @@ -714,6 +714,14 @@ def display_stats(self, colorized=True): else: print(colorize("*** ERROR: Statistics are unavailable ***", rgb=Color.ERROR)) + data_records = self.execute_sql_statement( + "SELECT ID FROM DATA;" + ) + nb_data_records = len(data_records) + title = colorize("Number of Data IDs: ", rgb=Color.FMKINFOGROUP) + content = colorize("{:d}".format(nb_data_records), rgb=Color.FMKSUBINFO) + print(title + content) + def export_data(self, first, last=None, colorized=True): colorize = self._get_color_function(colorized) diff --git a/framework/fuzzing_primitives.py b/framework/fuzzing_primitives.py index 801fb97..40fddfd 100644 --- a/framework/fuzzing_primitives.py +++ b/framework/fuzzing_primitives.py @@ -522,13 +522,12 @@ def interested_by(self, node): class BasicVisitor(NodeConsumerStub): - def init_specific(self, consume_also_singleton=False): + def init_specific(self): self._internals_criteria = dm.NodeInternalsCriteria(negative_node_kinds=[dm.NodeInternals_NonTerm]) - self.consume_also_singleton = consume_also_singleton self.firstcall = True def consume_node(self, node): - if node.is_exhausted() and not self.consume_also_singleton: + if node.is_exhausted() and not self.firstcall: # in this case we ignore the node return False else: @@ -741,6 +740,7 @@ def consume_node(self, node): node.unfreeze(ignore_entanglement=True) # we need to be sure that the current node is freezable node.set_attr(dm.NodeInternals.Freezable) + node.set_attr(dm.NodeInternals.LOCKED) return True else: diff --git a/framework/generic_data_makers.py b/framework/generic_data_makers.py index bbd058a..d71fe99 100644 --- a/framework/generic_data_makers.py +++ b/framework/generic_data_makers.py @@ -55,8 +55,10 @@ def truncate_info(info, max_size=60): gen_args = GENERIC_ARGS, args={'path': ('graph path regexp to select nodes on which' \ ' the disruptor should apply', None, str), + 'order': ('when set to True, the walking order is strictly guided ' \ + 'by the data structure. Otherwise, fuzz weight (if specified ' \ + 'in the data model) is used for ordering', True, bool), 'nt_only': ('walk through non-terminal nodes only', False, bool), - 'singleton': ('consume also terminal nodes with only one possible value', True, bool), 'fix_all': ('for each produced data, reevaluate the constraints on the whole graph', True, bool)}) class sd_iter_over_data(StatefulDisruptor): @@ -76,9 +78,9 @@ def set_seed(self, prev_data): prev_data.node.make_finite(all_conf=True, recursive=True) if self.nt_only: - consumer = NonTermVisitor() + consumer = NonTermVisitor(respect_order=self.order) else: - consumer = BasicVisitor(consume_also_singleton=self.singleton) + consumer = BasicVisitor(respect_order=self.order) consumer.set_node_interest(path_regexp=self.path) self.modelwalker = ModelWalker(prev_data.node, consumer, max_steps=self.max_steps, initial_step=self.init) self.walker = iter(self.modelwalker) @@ -693,7 +695,6 @@ def setup(self, dm, user_input): def disrupt_data(self, dm, target, prev_data): if prev_data.node: - # try to get more specific default conf if not self.provided_alt and self.available_confs: confs = prev_data.node.gather_alt_confs() @@ -713,10 +714,9 @@ def disrupt_data(self, dm, target, prev_data): prev_data.add_info("ALTERNATE CONF '{!s}' USED".format(self.conf)) - prev_data.node.unfreeze_all() prev_data.node.set_current_conf(self.conf, recursive=self.recursive, root_regexp=self.path) - - prev_data.node.get_value() + prev_data.node.unfreeze(recursive=True, reevaluate_constraints=True) + prev_data.node.freeze() else: prev_data.add_info('DONT_PROCESS_THIS_KIND_OF_DATA') diff --git a/framework/global_resources.py b/framework/global_resources.py index ac4e78b..bff8076 100644 --- a/framework/global_resources.py +++ b/framework/global_resources.py @@ -31,7 +31,7 @@ from libs.utils import ensure_dir, ensure_file -fuddly_version = '0.25' +fuddly_version = '0.25.1' framework_folder = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))) # framework_folder = os.path.dirname(framework.__file__) diff --git a/framework/monitor.py b/framework/monitor.py index a2df486..8b423b9 100644 --- a/framework/monitor.py +++ b/framework/monitor.py @@ -27,6 +27,8 @@ import time import traceback import re +import subprocess +import select from libs.external_modules import * from framework.global_resources import * @@ -855,6 +857,47 @@ def _read_serial(self, duration): return result +class Shell_Backend(Backend): + """ + Backend to execute shell commands locally + """ + def __init__(self, timeout=None, codec='latin_1'): + """ + Args: + timeout (float): timeout in seconds for reading the result of the command + codec (str): codec used by the monitored system to answer. + """ + Backend.__init__(self, codec=codec) + self._timeout = timeout + self._app = None + + def _start(self): + pass + + def _stop(self): + pass + + def _exec_command(self, cmd): + self._app = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + ready_to_read, ready_to_write, in_error = \ + select.select([self._app.stdout, self._app.stderr], [], [], self._timeout) + + if in_error: + # the command does not exist on the system + raise BackendError('Issue with file descriptors') + elif ready_to_read: + if len(ready_to_read) == 2: + err = ready_to_read[1].read() + if err.strip(): + raise BackendError('ERROR: {!s}'.format(ready_to_read[1].read())) + if ready_to_read[0]: + return ready_to_read[0].read() + else: + raise BackendError('BUG') + else: + return b'' + + class BackendError(Exception): pass class ProbePID(Probe): @@ -999,6 +1042,7 @@ def __init__(self): assert self.process_name != None assert self.backend != None self._saved_mem = None + self._max_mem = None Probe.__init__(self) def _get_mem(self): @@ -1022,6 +1066,7 @@ def _get_mem(self): def start(self, dm, target, logger): self.backend.start() + self._max_mem = None self._saved_mem = self._get_mem() self.reset() if self._saved_mem < 0: @@ -1050,16 +1095,17 @@ def main(self, dm, target, logger): self._max_mem = current_mem ok = True - info = "*** '{:s}' maximum RSS: {:d} ***\n".format(self.process_name, self._max_mem) + info = "*** '{:s}' Max RSS recorded: {:d} / Original " \ + "RSS: {:d} ***\n".format(self.process_name, self._max_mem, self._saved_mem) err_msg = '' if self.threshold is not None and self._max_mem > self.threshold: ok = False - err_msg += '\n*** Threshold exceeded ***' + err_msg += '\n*** Threshold exceeded (original RSS: {:d}) ***'.format(self._saved_mem) if self.tolerance is not None: delta = abs(self._max_mem - self._saved_mem) if (delta/float(self._saved_mem))*100 > self.tolerance: ok = False - err_msg += '\n*** Tolerance exceeded ***' + err_msg += '\n*** Tolerance exceeded (original RSS: {:d}) ***'.format(self._saved_mem) if not ok: status.set_status(-1) status.set_private_info(err_msg+'\n'+info) @@ -1069,9 +1115,10 @@ def main(self, dm, target, logger): return status def reset(self): + if self._max_mem is not None: + self._saved_mem = self._max_mem self._max_mem = self._saved_mem - def probe(project): def internal_func(probe_cls): project.monitor.add_probe(probe_cls(), blocking=False) diff --git a/framework/plumbing.py b/framework/plumbing.py index 6990b84..97f6753 100644 --- a/framework/plumbing.py +++ b/framework/plumbing.py @@ -305,7 +305,11 @@ def __reset_fmk_internals(self, reset_existing_seed=True): def _recompute_health_check_timeout(self, base_timeout, do_show=True): if base_timeout is not None: if base_timeout != 0: - self.set_health_check_timeout(base_timeout + 2.0, do_show=do_show) + if 0 < base_timeout < 1: + hc_timeout = base_timeout + 0.2 + else: + hc_timeout = base_timeout + 2.0 + self.set_health_check_timeout(hc_timeout, do_show=do_show) else: # base_timeout comes from feedback_timeout, if it is equal to 0 # this is a special meaning used internally to collect residual feedback. @@ -1061,14 +1065,21 @@ def dynamic_generator_ids(self): for genid in self.__dynamic_generator_ids[self.dm]: yield genid - @EnforceOrder(accepted_states=['S2']) def show_fmk_internals(self): + if not self.tg.supported_feedback_mode: + fbk_mode = 'Target does not provide feedback' + elif self.tg.wait_full_time_slot: + fbk_mode = self.tg.fbk_wait_full_time_slot_msg + else: + fbk_mode = self.tg.fbk_wait_until_recv_msg + print(colorize(FontStyle.BOLD + '\n-=[ FMK Internals ]=-\n', rgb=Color.INFO)) print(colorize(' Fuzz delay: ', rgb=Color.SUBINFO) + str(self._delay)) print(colorize(' Number of data sent in burst: ', rgb=Color.SUBINFO) + str(self._burst)) print(colorize(' Target health-check timeout: ', rgb=Color.SUBINFO) + str(self._hc_timeout)) print(colorize(' Target feedback timeout: ', rgb=Color.SUBINFO) + str(self.tg.feedback_timeout)) + print(colorize(' Target feedback mode: ', rgb=Color.SUBINFO) + fbk_mode) print(colorize(' Workspace enabled: ', rgb=Color.SUBINFO) + repr(self._wkspace_enabled)) print(colorize(' FmkDB enabled: ', rgb=Color.SUBINFO) + repr(self.fmkDB.enabled)) @@ -1405,7 +1416,7 @@ def set_fuzz_burst(self, val, do_record=False): def set_health_check_timeout(self, timeout, do_record=False, do_show=True): if timeout >= 0: self._hc_timeout = timeout - if do_show: + if do_show or do_record: self.lg.log_fmk_info('Target health-check timeout = {:.1f}s'.format(self._hc_timeout), do_record=do_record) return True @@ -1421,7 +1432,7 @@ def set_feedback_timeout(self, timeout, do_record=False, do_show=True): self._recompute_health_check_timeout(timeout, do_show=do_show) elif timeout >= 0: self.tg.set_feedback_timeout(timeout) - if do_show: + if do_show or do_record: self.lg.log_fmk_info('Target feedback timeout = {:.1f}s'.format(timeout), do_record=do_record) self._recompute_health_check_timeout(timeout, do_show=do_show) @@ -1430,6 +1441,25 @@ def set_feedback_timeout(self, timeout, do_record=False, do_show=True): self.lg.log_fmk_info('Wrong timeout value!', do_record=False) return False + @EnforceOrder(accepted_states=['S1','S2']) + def set_feedback_mode(self, mode, do_record=False, do_show=True): + ok = self.tg.set_feedback_mode(mode) + if not ok: + self.set_error('The target does not support this feedback Mode', code=Error.CommandError) + elif do_show or do_record: + if self.tg.wait_full_time_slot: + msg = 'Feedback Mode = ' + self.tg.fbk_wait_full_time_slot_msg + else: + msg = 'Feedback Mode = ' + self.tg.fbk_wait_until_recv_msg + self.lg.log_fmk_info(msg, do_record=do_record) + + @EnforceOrder(accepted_states=['S1','S2']) + def switch_feedback_mode(self, do_record=False, do_show=True): + if self.tg.wait_full_time_slot: + self.set_feedback_mode(Target.FBK_WAIT_UNTIL_RECV, do_record=do_record, do_show=do_show) + else: + self.set_feedback_mode(Target.FBK_WAIT_FULL_TIME, do_record=do_record, do_show=do_show) + # Used to introduce some delay after sending data def __delay_fuzzing(self): ''' @@ -1497,6 +1527,8 @@ def _do_sending_and_logging_init(self, data_list): for d in data_list: if d.feedback_timeout is not None: self.set_feedback_timeout(d.feedback_timeout) + if d.feedback_mode is not None: + self.set_feedback_mode(d.feedback_mode) blocked_data = list(filter(lambda x: x.is_blocked(), data_list)) data_list = list(filter(lambda x: not x.is_blocked(), data_list)) @@ -1787,6 +1819,7 @@ def send_data(self, data_list, add_preamble=False): if not self._is_data_valid(data_list): self.set_error("send_data(): Data has been provided empty --> won't be sent", code=Error.DataInvalid) + self.mon.notify_error() return None data_list = self._do_before_sending_data(data_list) @@ -1794,11 +1827,13 @@ def send_data(self, data_list, add_preamble=False): if not data_list: self.set_error("send_data(): No more data to send", code=Error.NoMoreData) + self.mon.notify_error() return None if not self._is_data_valid(data_list): self.set_error("send_data(): Data became empty --> won't be sent", code=Error.DataInvalid) + self.mon.notify_error() return None self._sending_error = False @@ -4203,6 +4238,14 @@ def do_set_feedback_timeout(self, line): self.__error = False return False + def do_switch_feedback_mode(self, line): + ''' + Switch target feedback mode between: + - wait for the full time slot allocated for feedback retrieval + - wait until the target has send something back to us + ''' + self.fz.switch_feedback_mode(do_record=True, do_show=True) + return False def do_set_health_timeout(self, line): ''' diff --git a/framework/scenario.py b/framework/scenario.py index 944efa7..06bbd88 100644 --- a/framework/scenario.py +++ b/framework/scenario.py @@ -54,6 +54,7 @@ def __init__(self, process, seed=None, auto_regen=False): self.auto_regen = auto_regen self.outcomes = None self.feedback_timeout = None + self.feedback_mode = None self._process = [process] self._process_idx = 0 self._blocked = False @@ -92,6 +93,7 @@ def __copy__(self): new_datap._process_idx = self._process_idx new_datap._blocked = self._blocked new_datap.feedback_timeout = self.feedback_timeout + new_datap.feedback_mode = self.feedback_mode return new_datap @@ -103,7 +105,8 @@ def __init__(self, data, period=None): class Step(object): - def __init__(self, data_desc=None, final=False, fbk_timeout=None, + def __init__(self, data_desc=None, final=False, + fbk_timeout=None, fbk_mode=None, set_periodic=None, clear_periodic=None, step_desc=None): self.final = final @@ -123,7 +126,9 @@ def __init__(self, data_desc=None, final=False, fbk_timeout=None, self.make_free() + # need to be set after self._data_desc self.feedback_timeout = fbk_timeout + self.feedback_mode = fbk_mode self._dm = None self._scenario_env = None @@ -178,6 +183,16 @@ def feedback_timeout(self, fbk_timeout): if isinstance(self._data_desc, (Data, DataProcess)): self._data_desc.feedback_timeout = fbk_timeout + @property + def feedback_mode(self): + return self._feedback_mode + + @feedback_mode.setter + def feedback_mode(self, fbk_mode): + self._feedback_mode = fbk_mode + if isinstance(self._data_desc, (Data, DataProcess)): + self._data_desc.feedback_mode = fbk_mode + @property def transitions(self): for tr in self._transitions: @@ -240,6 +255,8 @@ def get_data(self): d.make_blocked() if self._feedback_timeout is not None: d.feedback_timeout = self._feedback_timeout + if self._feedback_mode is not None: + d.feedback_mode = self._feedback_mode return d @@ -277,7 +294,7 @@ def __copy__(self): new_periodic_to_rm = copy.copy(self._periodic_data_to_remove) new_transitions = copy.copy(self._transitions) new_step = type(self)(data_desc=copy.copy(self._data_desc), final=self.final, - fbk_timeout=self.feedback_timeout, + fbk_timeout=self.feedback_timeout, fbk_mode=self.feedback_mode, set_periodic=copy.copy(self._periodic_data), step_desc=self._step_desc) new_step._node = None @@ -289,14 +306,15 @@ def __copy__(self): class FinalStep(Step): - def __init__(self, data_desc=None, final=False, fbk_timeout=None, + def __init__(self, data_desc=None, final=False, fbk_timeout=None, fbk_mode=None, set_periodic=None, clear_periodic=None , step_desc=None): Step.__init__(self, final=True) class NoDataStep(Step): - def __init__(self, data_desc=None, final=False, fbk_timeout=None, + def __init__(self, data_desc=None, final=False, fbk_timeout=None, fbk_mode=None, set_periodic=None, clear_periodic=None, step_desc=None): - Step.__init__(self, data_desc=Data(''), final=final, fbk_timeout=fbk_timeout, + Step.__init__(self, data_desc=Data(''), final=final, + fbk_timeout=fbk_timeout, fbk_mode=fbk_mode, set_periodic=set_periodic, clear_periodic=clear_periodic, step_desc=step_desc) self.make_blocked() diff --git a/framework/target.py b/framework/target.py index 9dca050..25a403f 100644 --- a/framework/target.py +++ b/framework/target.py @@ -54,6 +54,15 @@ class Target(object): Class abstracting the target we interact with. ''' feedback_timeout = None + + FBK_WAIT_FULL_TIME = 1 + fbk_wait_full_time_slot_msg = 'Wait for the full time slot allocated for feedback retrieval' + FBK_WAIT_UNTIL_RECV = 2 + fbk_wait_until_recv_msg = 'Wait until the target has sent something back to us' + + _feedback_mode = None + supported_feedback_mode = [] + _logger = None _probes = None _send_data_lock = threading.Lock() @@ -206,6 +215,17 @@ def _set_feedback_timeout_specific(self, fbk_timeout): ''' pass + def set_feedback_mode(self, mode): + if mode in self.supported_feedback_mode: + self._feedback_mode = mode + return True + else: + return False + + @property + def wait_full_time_slot(self): + return self._feedback_mode == Target.FBK_WAIT_FULL_TIME + def get_description(self): return None @@ -305,6 +325,9 @@ def cleanup(self): class EmptyTarget(Target): + _feedback_mode = None + supported_feedback_mode = [] + def send_data(self, data, from_fmk=False): pass @@ -314,6 +337,9 @@ def send_multiple_data(self, data_list, from_fmk=False): class TestTarget(Target): + _feedback_mode = None + supported_feedback_mode = [] + def __init__(self, recover_ratio=100): self._cpt = None self._recover_ratio = recover_ratio @@ -353,6 +379,9 @@ class NetworkTarget(Target): CHUNK_SZ = 2048 _INTERNALS_ID = 'NetworkTarget()' + _feedback_mode = Target.FBK_WAIT_FULL_TIME + supported_feedback_mode = [Target.FBK_WAIT_FULL_TIME, Target.FBK_WAIT_UNTIL_RECV] + def __init__(self, host='localhost', port=12345, socket_type=(socket.AF_INET, socket.SOCK_STREAM), data_semantics=UNKNOWN_SEMANTIC, server_mode=False, hold_connection=False, mac_src=None, mac_dst=None): @@ -1121,10 +1150,11 @@ def _check_and_handle_obsolete_socket(skt, error=None, error_list=None): chunks[fd].append(pre_fbk[fd]) socket_errors = [] + has_read = False while dont_stop: ready_to_read = [] - for fd, ev in epobj.poll(timeout=0.2): + for fd, ev in epobj.poll(timeout=0.1): skt = fileno2fd[fd] if ev != select.EPOLLIN: _check_and_handle_obsolete_socket(skt, error=ev, error_list=socket_errors) @@ -1178,6 +1208,9 @@ def _check_and_handle_obsolete_socket(skt, error=None, error_list=None): bytes_recd[s] = bytes_recd[s] + len(chunk) chunks[s].append(chunk) + has_read = True + + if fbk_sockets: for s in fbk_sockets: if s in ready_to_read: @@ -1191,7 +1224,7 @@ def _check_and_handle_obsolete_socket(skt, error=None, error_list=None): else: dont_stop = False - if duration > fbk_timeout: + if duration > fbk_timeout or (has_read and not self.wait_full_time_slot): dont_stop = False else: @@ -1436,6 +1469,10 @@ def get_description(self): class PrinterTarget(Target): + # No target feedback implemented + _feedback_mode = None + supported_feedback_mode = [] + def __init__(self, tmpfile_ext): self.__suffix = '{:0>12d}'.format(random.randint(2**16, 2**32)) self.__feedback = TargetFeedback() @@ -1524,6 +1561,9 @@ def send_data(self, data, from_fmk=False): class LocalTarget(Target): + _feedback_mode = Target.FBK_WAIT_UNTIL_RECV + supported_feedback_mode = [Target.FBK_WAIT_UNTIL_RECV] + def __init__(self, tmpfile_ext, target_path=None): self.__suffix = '{:0>12d}'.format(random.randint(2**16, 2**32)) self.__app = None @@ -1621,7 +1661,8 @@ def cleanup(self): finally: self._data_sent = False - def get_feedback(self, delay=0.2): + def get_feedback(self, timeout=0.2): + timeout = self.feedback_timeout if timeout is None else timeout if self._feedback_computed: return self.__feedback else: @@ -1641,7 +1682,7 @@ def get_feedback(self, delay=0.2): "Negative return status ({:d})".format(exit_status)) err_detected = False - ret = select.select([self.__app.stdout, self.__app.stderr], [], [], delay) + ret = select.select([self.__app.stdout, self.__app.stderr], [], [], timeout) if ret[0]: byte_string = b'' for fd in ret[0][:-1]: @@ -1670,6 +1711,9 @@ def get_feedback(self, delay=0.2): class SIMTarget(Target): delay_between_write = 0.1 # without, it seems some commands can be lost + _feedback_mode = Target.FBK_WAIT_FULL_TIME + supported_feedback_mode = [Target.FBK_WAIT_FULL_TIME] + def __init__(self, serial_port, baudrate, pin_code, targeted_tel_num, codec='latin_1'): self.serial_port = serial_port self.baudrate = baudrate diff --git a/framework/value_types.py b/framework/value_types.py index 4c8fb80..004f734 100644 --- a/framework/value_types.py +++ b/framework/value_types.py @@ -1489,31 +1489,30 @@ def _unconvert_value(self, val): return int(val) def _convert_value(self, val): - return self._str2bytes(str(val)) - - def pretty_print(self, max_size=None): - if self.drawn_val is None: - self.get_value() - - return str(self.drawn_val) - - def _str2bytes(self, val): - if isinstance(val, (list, tuple)): - b = [v.encode('utf8') for v in val] - else: - b = val.encode('utf8') - return b + return str(val).encode('utf8') #class Fuzzy_INT_str(Fuzzy_INT, metaclass=meta_int_str): class Fuzzy_INT_str(with_metaclass(meta_int_str, Fuzzy_INT)): - values = [0, 2 ** 32 - 1, 2 ** 32] + values = [0, -1, -2**32, 2 ** 32 - 1, 2 ** 32, + b'%n'*8, b'%n'*100, b'\"%n\"'*100, + b'%s'*8, b'%s'*100, b'\"%s\"'*100] def is_compatible(self, integer): return True + def pretty_print(self, max_size=None): + if self.drawn_val is None: + self.get_value() + + return str(self.drawn_val) + def _convert_value(self, val): - return str(val) + if isinstance(val, int): + return str(val).encode('utf8') + else: + assert isinstance(val, bytes) + return val diff --git a/projects/generic/standard_proj.py b/projects/generic/standard_proj.py index 17e5f1b..3f59e36 100644 --- a/projects/generic/standard_proj.py +++ b/projects/generic/standard_proj.py @@ -39,6 +39,13 @@ printer1_tg.set_target_ip('127.0.0.1') printer1_tg.set_printer_name('PDF') +local_backend = Shell_Backend(timeout=2) +@blocking_probe(project) +class display_mem_check(ProbeMem): + backend = local_backend + process_name = 'display' + tolerance = 10 + local_tg = LocalTarget(tmpfile_ext='.png') local_tg.set_target_path('/usr/bin/display') @@ -62,7 +69,7 @@ socket_type=(socket.AF_PACKET, socket.SOCK_RAW, socket.htons(ETH_P_ALL)), hold_connection=True, server_mode=False) -targets = [local_tg, +targets = [(local_tg, (display_mem_check, 0.1)), local2_tg, local3_tg, printer1_tg, net_tg, netsrv_tg, rawnetsrv_tg] diff --git a/test/integration/test_integration.py b/test/integration/test_integration.py index ad2b107..9f3b81f 100644 --- a/test/integration/test_integration.py +++ b/test/integration/test_integration.py @@ -934,7 +934,7 @@ def test_TypedNode_1(self): break print('\nTurn number when Node has changed: %r, number of test cases: %d' % (turn_nb_list, i)) - good_list = [1, 13, 23, 33, 43, 52, 61, 71, 81, 91, 103, 113, 123, 133, 143, 152, 162, 172, 182, 191, 200, 206, 221] + good_list = [1, 13, 23, 33, 43, 52, 61, 71, 81, 91, 103, 113, 123, 133, 143, 152, 162, 172, 182, 191, 200, 214, 229] msg = "If Fuzzy_.values have been modified in size, the good_list should be updated.\n" \ "If BitField are in random mode [currently put in determinist mode], the fuzzy_mode can produce more" \ " or less value depending on drawn value when .get_value() is called (if the drawn value is" \ @@ -1472,7 +1472,7 @@ def test_NodeConsumerStub_2(self): def test_BasicVisitor(self): nt = self.dm.get_data('Simple') - default_consumer = BasicVisitor(respect_order=True, consume_also_singleton=False) + default_consumer = BasicVisitor(respect_order=True) for rnode, consumed_node, orig_node_val, idx in ModelWalker(nt, default_consumer, make_determinist=True, max_steps=200): print(colorize('[%d] ' % idx + repr(rnode.to_bytes()), rgb=Color.INFO)) @@ -1480,7 +1480,7 @@ def test_BasicVisitor(self): print('***') nt = self.dm.get_data('Simple') - default_consumer = BasicVisitor(respect_order=False, consume_also_singleton=False) + default_consumer = BasicVisitor(respect_order=False) for rnode, consumed_node, orig_node_val, idx in ModelWalker(nt, default_consumer, make_determinist=True, max_steps=200): print(colorize('[%d] ' % idx + repr(rnode.to_bytes()), rgb=Color.INFO)) @@ -1581,6 +1581,8 @@ def test_basics(self): mh = ModelHelper(delayed_jobs=True) data = mh.create_graph_from_desc(shape_desc) + bv_data = data.get_clone() + nt_data = data.get_clone() raw_vals = [ b' [!] ++++++++++ [!] ::=:: [!] ', @@ -1703,6 +1705,25 @@ def test_basics(self): self.assertEqual(idx, 102) # should be even + print('***') + idx = 0 + bv_consumer = BasicVisitor(respect_order=True) + for rnode, consumed_node, orig_node_val, idx in ModelWalker(bv_data, bv_consumer, + make_determinist=True, + max_steps=100): + print(colorize('[%d] ' % idx + rnode.to_ascii(), rgb=Color.INFO)) + self.assertEqual(idx, 6) + + print('***') + idx = 0 + nt_consumer = NonTermVisitor(respect_order=True) + for rnode, consumed_node, orig_node_val, idx in ModelWalker(nt_data, nt_consumer, + make_determinist=True, + max_steps=100): + print(colorize('[%d] ' % idx + rnode.to_ascii(), rgb=Color.INFO)) + self.assertEqual(idx, 6) # shall be equal to the previous test + + def test_TypedNodeDisruption_1(self): nt = self.dm.get_data('Simple') tn_consumer = TypedNodeDisruption() @@ -1860,6 +1881,71 @@ def setUpClass(cls): def setUp(self): pass + def test_djobs(self): + tag_desc = \ + {'name': 'tag', + 'contents': [ + {'name': 'type', + 'contents': UINT16_be(values=[0x0101,0x0102,0x0103,0x0104, 0]), + 'absorb_csts': AbsFullCsts()}, + {'name': 'len', + 'contents': UINT16_be(), + 'absorb_csts': AbsNoCsts()}, + {'name': 'value', + 'contents': [ + {'name': 'v000', # Final Tag (optional) + 'exists_if': (IntCondition(0), 'type'), + 'sync_enc_size_with': 'len', + 'contents': String(size=0)}, + {'name': 'v101', # Service Name + 'exists_if': (IntCondition(0x0101), 'type'), + 'sync_enc_size_with': 'len', + 'contents': String(values=[u'my \u00fcber service'], codec='utf8'), + }, + {'name': 'v102', # AC name + 'exists_if': (IntCondition(0x0102), 'type'), + 'sync_enc_size_with': 'len', + 'contents': String(values=['AC name'], codec='utf8'), + }, + {'name': 'v103', # Host Identifier + 'exists_if': (IntCondition(0x0103), 'type'), + 'sync_enc_size_with': 'len', + 'contents': String(values=['Host Identifier']), + }, + {'name': 'v104', # Cookie + 'exists_if': (IntCondition(0x0104), 'type'), + 'sync_enc_size_with': 'len', + 'contents': String(values=['Cookie'], min_sz=0, max_sz=1000), + }, + ]} + ]} + + mh = ModelHelper(delayed_jobs=True) + d = mh.create_graph_from_desc(tag_desc) + d.make_determinist(recursive=True) + d2 = d.get_clone() + d3 = d.get_clone() + + d.freeze() + d['.*/value$'].unfreeze() + d_raw = d.to_bytes() + d.show() + + d2.freeze() + d2['.*/value$'].unfreeze() + d2['.*/value$'].freeze() + d2_raw = d2.to_bytes() + d2.show() + + d3.freeze() + d3['.*/value$'].unfreeze() + d3['.*/len$'].unfreeze() + d3_raw = d3.to_bytes() + d3.show() + + self.assertEqual(d_raw, d2_raw) + self.assertEqual(d_raw, d3_raw) + def test_absorb_nonterm_1(self): nint_1 = Node('nint1', value_type=UINT16_le(values=[0xabcd])) nint_2 = Node('nint2', value_type=UINT8(values=[0xf])) diff --git a/test/unit/test_data_model.py b/test/unit/test_data_model.py index 2de4f6f..0bb7244 100644 --- a/test/unit/test_data_model.py +++ b/test/unit/test_data_model.py @@ -38,7 +38,7 @@ def side_effect(idx): cls.node = mock.Mock() cls.node.get_subfield = mock.MagicMock(side_effect=side_effect) - + cls.node.is_genfunc = mock.MagicMock(return_value=False) @ddt.data((1, 1), (1, [1]), ([1], [1]), (1, (1,)), ((1,), (1,)), diff --git a/test/unit/test_data_model_helpers.py b/test/unit/test_data_model_helpers.py index 8f055b9..8d9a3cf 100644 --- a/test/unit/test_data_model_helpers.py +++ b/test/unit/test_data_model_helpers.py @@ -87,6 +87,13 @@ def test_quantifiers(self, test_case): 'nodes': [{"values": [u"\u0443"]}, {"values": [u"abcd"]}]}, {'regex': u"hi(ab\u0443cd)", "charset": MH.Charset.UNICODE, 'nodes': [{"values": [u"hi"]}, {"values": [u"ab\u0443cd"]}]}, + {'regex': u"(333|444)|foo-bar|\d|[th|is]", + 'nodes': [ + {"type": fvt.INT_str, "values": [333,444]}, + {"values": [u"foo-bar"]}, + {"alphabet": "0123456789"}, + {"alphabet": "th|is"}]}, + ) def test_escape(self, test_case): self.assert_regex_is_valid(test_case)