def preprocess( cls, capture: Capture, expected_frames_pattern: list_of(Value) ) -> (list_of(Conversation), list_of(Frame)): """ Pre-process and filter the frames of the capture into test case related conversations. This has to be implemented into the protocol's common test case class. This method depends on the protocol features, so it cannot be implemented in a generic way. """ raise NotImplementedError()
def import_test_cases(self, testcases: optional(list_of(str)) = None) -> list: """ Imports test cases classes from TESTCASES_DIR :param testcases: The wanted test cases as a list of string :return: List of test cases class in the same order than the param list :raises FileNotFoundError: If no test case was found .. note:: Assumptions are the following: - Test cases are defined inside a .py file, each file contains only one test case - All test cases must be named td_* - All test cases are contained into ttproto/[env]/testcases - Filenames corresponds to the TC id in lower case - Class names corresponds to the TC id """ # The return values tc_fetched = [] # If no TCs provided, fetch all the test cases found if not testcases: tc_fetched = self.__get_testcases_from_pathname(EVERY_TC_WILDCARD) # If testcases list are provided, fetch those else: # For every test case given for test_case_name in testcases: tc_name_query = test_case_name.lower() + TC_FILE_EXTENSION tc_fetched += self.__get_testcases_from_pathname(tc_name_query) # Return the test cases classes return tc_fetched
def get_dissection( self, protocol: optional(is_protocol) = None, ) -> list_of(OrderedDict): """ Function to get dissection of a capture as a list of frames represented as strings :param protocol: Protocol class for filtering purposes :type protocol: type :raises TypeError: If protocol is not a protocol class :raises ReaderError: If the reader couldn't process the file :return: A list of Frame represented as API's dict form :rtype: [OrderedDict] """ # log.debug('Starting dissection.') # Check the protocol is one entered if all((protocol, not is_protocol(protocol))): raise TypeError(protocol.__name__ + ' is not a protocol class') fs = self.frames # For speeding up the process with Data.disable_name_resolution(): # Filter the frames for the selected protocol if protocol: fs, _ = Frame.filter_frames(fs, protocol) if fs is None: raise Error('Empty capture cannot be dissected') # Then return the list of dictionary frame representation return [frame.dict() for frame in fs]
def dissect( self, protocol: optional(is_protocol) = None) -> list_of(OrderedDict): """ The dissect function to dissect a pcap file into list of frames :param protocol: Protocol class for filtering purposes :type protocol: type :raises TypeError: If protocol is not a protocol class :raises ReaderError: If the reader couldn't process the file :return: A list of Frame represented as API's dict form :rtype: [OrderedDict] """ # log.debug('Starting dissection.') # Check the protocol is one entered if all((protocol, not is_protocol(protocol))): raise TypeError(protocol.__name__ + ' is not a protocol class') # For speeding up the process with Data.disable_name_resolution(): # Get the list of frames frames = self.__capture.frames # Filter the frames for the selected protocol if protocol is not None: frames, _ = Frame.filter_frames(frames, protocol) # Then return the list of dictionary frame representation return [frame.dict() for frame in frames]
def get_implemented_protocols(cls) -> list_of(type): """ Allow to get the implemented protocols :return: Implemented protocols :rtype: [type] """ logging.warning( "Deprecation warning in favour of package's get_dissectable_protocols method!" ) # Singleton pattern if cls.__implemented_protocols is None: # Just directly get the PacketValue and InetPacketValue subclasses cls.__implemented_protocols = [] # cls.__implemented_protocols += PacketValue.__subclasses__() # cls.__implemented_protocols += InetPacketValue.__subclasses__() # NOTE: This may ben needed if we change the protocol getter system add_subclass(cls.__implemented_protocols, PacketValue) # Remove the InetPacketValue class cls.__implemented_protocols.remove(InetPacketValue) # Return the singleton value return cls.__implemented_protocols
def get_test_steps(tc: str) -> list_of(OrderedDict): """ Get an OrderedDict representing the different steps of a test case :param tc: The id of the test case :type tc: str :return: The steps of a TC as a list of OrderedDict :rtype: [OrderedDict] """ # The return list of OrderedDict steps = [] # Get the test case informations raw_tcs = Analyzer('tat_coap').get_implemented_testcases([tc], True) assert len(raw_tcs) == 1 # Parse the documentation with yaml reader and get it as dictionnary doc_reader = YamlReader(raw_tcs[0][3], raw_text=True) doc_dict = doc_reader.as_dict # For every step, put its informations inside the steps dict for step_id, step in enumerate(doc_dict[tc]['seq']): step_dict = OrderedDict() step_dict['_type'] = 'step' step_dict['step_id'] = step_id step_dict['step_type'], step_dict['step_info'] = step.popitem() steps.append(step_dict) # Return the step list of this tc return steps
def get_implemented_testcases( self, testcases: optional(list_of(str)) = None, verbose: bool = False ) -> list_of((str, str, str, str)): """ Get more informations about the test cases :param testcases: A list of test cases to get their informations :param verbose: True if we want more informations about the TC :type testcase_id: optional([str]) :type verbose: bool :raises FileNotFoundError: If one of the test case is not found :return: List of descriptions of test cases composed of: - tc_identifier - tc_objective - tc_sourcecode - tc_doc :rtype: [(str, str, str, str)] """ # The return value ret = [] # Get the tc classes tc_classes = self.import_test_cases(testcases) # Add the infos of each of them to the return value for tc in tc_classes: # If verbose is asked, we provide the source code and doc too source_code = '' source_doc = '' if verbose: source_code = inspect.getsource(tc) source_doc = inspect.getdoc(tc) # Add the tuple to the return value ret.append( (tc.__name__, tc.get_test_purpose(), source_code, source_doc) ) # Return the list of tuples return ret
def get_nodes_identification_templates(cls) -> list_of(Node): """ Get the nodes of this test case. This has to be be implemented into each test cases class. :return: The nodes of this TC :rtype: [Node] """ raise NotImplementedError()
def get_stimulis(cls) -> list_of(Value): """ Get the stimulis of this test case. This has to be be implemented into each test cases class. :return: The stimulis of this TC :rtype: [Value] """ raise NotImplementedError()
def summary(self, protocol: optional(is_protocol) = None) -> list_of((int, str)): """ The summaries function to get the summary of frames :param protocol: Protocol class for filtering purposes :type protocol: type :Example: from ttproto.core.lib.all import Ieee802154 for s in dissector.summary(protocol = Ieee802154): print(s) :raises TypeError: If protocol is not a protocol class :raises ReaderError: If the reader couldn't process the file :return: Basic informations about frames like the underlying example :rtype: [(int, str)] :Example: [ (13, '[127.0.0.1 -> 127.0.0.1] CoAP [CON 38515] GET /test'), (14, '[127.0.0.1 -> 127.0.0.1] CoAP [ACK 38515] 2.05 Content'), (21, '[127.0.0.1 -> 127.0.0.1] CoAP [CON 38516] PUT /test'), (22, '[127.0.0.1 -> 127.0.0.1] CoAP [ACK 38516] 2.04 Changed')] ] .. note:: With the protocol option we can filter the response """ if all((protocol, not is_protocol(protocol))): raise TypeError(protocol.__name__ + ' is not a protocol class') fs = self.frames # For speeding up the process with Data.disable_name_resolution(): # Filter the frames for the selected protocol if protocol: fs, _ = Frame.filter_frames(fs, protocol) if fs is None: raise Error('Empty capture cannot be dissected') # Return list of frames summary return [frame.summary() for frame in fs]
def summary(self, protocol: optional(is_protocol) = None) -> list_of((int, str)): """ The summaries function to get the summary of frames :param protocol: Protocol class for filtering purposes :type protocol: type :Example: from ttproto.core.lib.all import Ieee802154 for s in dissector.summary(protocol = Ieee802154): print(s) :raises TypeError: If protocol is not a protocol class :raises ReaderError: If the reader couldn't process the file :return: Basic informations about frames like the underlying example :rtype: [(int, str)] :Example: [ (13, '[127.0.0.1 -> 127.0.0.1] CoAP [CON 38515] GET /test'), (14, '[127.0.0.1 -> 127.0.0.1] CoAP [ACK 38515] 2.05 Content'), (21, '[127.0.0.1 -> 127.0.0.1] CoAP [CON 38516] PUT /test'), (22, '[127.0.0.1 -> 127.0.0.1] CoAP [ACK 38516] 2.04 Changed')] ] .. todo:: Filter uninteresting frames ? (to decrease the load) .. note:: With the protocol option we can filter """ # Check the protocol if all((protocol, not is_protocol(protocol))): raise TypeError(protocol.__name__ + ' is not a protocol class') # Disable the name resolution in order to improve performances with Data.disable_name_resolution(): # Get the frames from the capture frames = self.__capture.frames # Filter the frames for the selected protocol if protocol is not None: frames, _ = Frame.filter_frames(frames, protocol) # Then give the summary of every frames return [frame.summary() for frame in frames]
def filter_frames( cls, frames: list_of(this_class), protocol: is_protocol ) -> (list_of(this_class), list_of(this_class)): """ Allow to filter frames on a protocol :param frames: The frames to filter :param protocol: Protocol class for filtering purposes :type frames: [Frame] :type protocol: type :raises TypeError: If protocol is not a protocol class or if the list contains a non Frame object :return: A tuple containing the filtered frames and the ignored ones :rtype: ([Frame], [Frame]) """ # The return list filtered_frames = [] ignored_frames = [] # Check the protocol is one entered if not is_protocol(protocol): raise TypeError(protocol.__name__ + ' is not a protocol class') # Remove all frames which doesn't include this protocol for frame in frames: # If an element of the list isn't a Frame if not isinstance(frame, Frame): raise TypeError('Parameter frames contains a non Frame object') # If the protocol is contained into this frame if protocol in frame: filtered_frames.append(frame) else: ignored_frames.append(frame) # Return the newly created list return filtered_frames, ignored_frames
def get_dissectable_protocols() -> list_of(type): """ Return list of the implemented protocols (PacketValue classes) :return: Implemented protocols :rtype: [type] """ # Just directly get the PacketValue and InetPacketValue subclasses implemented_protocols = [] add_subclass(implemented_protocols, PacketValue) # Remove the InetPacketValue class implemented_protocols.remove(InetPacketValue) return implemented_protocols
def get_dissection_simple_format( self, protocol: optional(is_protocol) = None, ) -> list_of(str): """ Function to get dissection of a capture as a list of frames represented as strings :param protocol: Protocol class for filtering purposes :type protocol: type :raises TypeError: If protocol is not a protocol class :raises ReaderError: If the reader couldn't process the file :return: A list of Frame represented as plain non-structured text :rtype: [str] """ # log.debug('Starting dissection.') # Check the protocol is one entered if all((protocol, not is_protocol(protocol))): raise TypeError(protocol.__name__ + ' is not a protocol class') fs = self.frames # For speeding up the process with Data.disable_name_resolution(): # Filter the frames for the selected protocol if protocol: fs, _ = Frame.filter_frames(fs, protocol) if fs is None: raise Error('Empty capture cannot be dissected') # fixme modify Message class from ttproto.data structure so I can get text display wihtout this patch class WritableObj(object): def __init__(self, text=''): self.val = text def __str__(self): return self.val def write(self, text): self.val += text frame_dissection_list = [] for f in fs: text_output = WritableObj() f.message.display(output=text_output) frame_dissection_list.append(str(text_output)) # Then return the list of frames,each as a simple text dissection return frame_dissection_list
def __init__(self, nodes: list_of(Node)): """ Function to initialize a Conversation object with its nodes :param nodes: The communicating nodes :type nodes: [Node] :raises ValueError: If not enough nodes received (less than 2) """ # If not enough nodes if len(nodes) < 2: raise ValueError( 'At least 2 nodes are required but %d received' % len(nodes) ) # The node dictionary self._nodes = {} for node in nodes: self._nodes[node.name] = node.value
def run_test_case(self) -> ( str, list_of(int), str, list_of((str, str)), list_of((type, Exception, is_traceback)) ): """ Run the test case :return: A tuple with the informations about the test results which are - The verdict as a string - The list of the result important frames - A string with the log of the test case - A list of all the partial verdicts and their messages - A list of typles representing the exceptions that occurred :rtype: (str, [int], str,[(str,str)], [(type, Exception, traceback)]) """ # Next line is before for 6lowpan TC, where nodes_identification_templates # is not generic. TestCase.get_nodes_identification_templates = self.get_nodes_identification_templates # Pre-process / filter conversations corresponding to the TC self._conversations, self._ignored = self.preprocess( capture=self._capture, expected_frames_pattern=self.get_stimulis() ) # print("----conversations----") # print(self._conversations) # print("----ignored----") # print(self._ignored) if self._conversations == [[]] or self._conversations == []: self.set_verdict( 'inconclusive', 'Capture doesnt match expected pattern: \n\tgot %s, \n\texpected %s' % (str(self._capture.frames), str(self.get_stimulis())) ) else: # Run the test case for every conversations for conv in self._conversations: if logger.getEffectiveLevel() == logging.DEBUG: for frame in conv: logger.debug(frame) try: # Get an iterator on the current conversation frames # and its list of nodes self._iter = iter(conv) self._nodes = conv.nodes self.next() # Run the test case self.run() except self.Stop: # Ignore this testcase result if the first frame gives an # inconclusive verdict if all(( self._verdict.get_value() == 'inconclusive', self._frame == conv[0] )): self.set_verdict('none', 'no match') except Exception as e: # Get the execution information, it's a tuple with # - The type of the exception being handled # - The exception instance # - The traceback object _exception_type, _exception_value, _exception_traceback = sys.exc_info() logger.error(e) traceback.print_exc(file=sys.stdout) # Add those exception information to the list self._exceptions.append(( _exception_type, _exception_value, _exception_traceback )) # Put the verdict and log the exception self.set_verdict('error', 'unhandled exception') self.log(_exception_value) # Return the results return ( self._verdict.get_value(), self._failed_frames, self._text, self._verdict.get_traceback(), self._exceptions, )
def analyse( self, filename: str, tc_id: str ) -> (str, str, list_of(int), str, list_of((str, str)), list_of((type, Exception, is_traceback))): """ Analyse a dump file associated to a test case :param filename: The name of the file to analyse :param tc_id: The unique id of the test case to confront the given file :type filename: str :type tc_id: str :return: A tuple with the information about the analysis results: - The id of the test case - The verdict as a string - The list of the result important frames - A string with logs - A list of all the partial verdicts - A list of tuples representing the exceptions that occurred :rtype: (str, str, [int], str,[(str, str)], [(type, Exception, traceback)]) :raises FileNotFoundError: If the test env of the tc is not found :raises ReaderError: If the capture didn't manage to read and decode :raises ObsoleteTestCase: If the test case if obsolete .. example:: ('TD_COAP_CORE_03', 'fail', [21, 22], [('fail', "some comment"),('fail', "some other comment")] , 'verdict description', '') .. note:: Allows multiple occurrences of executions of the testcase, returns as verdict: - fail: if at least one on the occurrences failed - inconclusive: if all occurrences returned a inconclusive verdict - pass: all occurrences are inconclusive or at least one is PASS and the rest is inconclusive """ # Get the test case class test_case_class = self.import_test_cases([tc_id]) assert len(test_case_class) == 1 test_case_class = test_case_class[0] # Disable name resolution for performance improvements with Data.disable_name_resolution(): # Get the capture from the file capture = Capture(filename) # Initialize the TC with the list of conversations test_case = test_case_class(capture) verdict, rev_frames, log, partial_verdicts, exceps = test_case.run_test_case() # print('##### capture') # print(capture) # print('#####') # # # Here we execute the test case and return the result # # print('##### Verdict given') # print(verdict) # print('#####') # print('##### Review frames') # print(rev_frames) # print('#####') # print('##### Text') # print(log, partial_verdicts) # print('#####') # print('##### Exceptions') # print(exceptions) # print('#####') return tc_id, verdict, rev_frames, log, partial_verdicts, exceps
def preprocess( cls, capture: Capture, expected_frames_pattern: list_of(Value) ) -> (list_of(Conversation), list_of(Frame)): """ Preprocess and filter the frames of the capture into test case related conversations. :param Capture: The capture which will be filtered/preprocessed :return: """ protocol = SixlowpanTestCase.get_protocol() nodes = TestCase.get_nodes_identification_templates() conversations = [] ignored = [] # TODO what happens if no protocol declared on the test case? if not nodes or len(nodes) < 2: raise ValueError( 'Expected at leaset two nodes declaration from the test case') if not protocol: raise ValueError( 'Expected a protocol under test declaration from the test case' ) # If there is no stimuli at all if not expected_frames_pattern or len(expected_frames_pattern) == 0: raise NoStimuliFoundForTestcase( 'Expected stimuli declaration from the test case') # Get the frames filtered on the protocol frames, ignored = Frame.filter_frames(capture.frames, protocol) # Get a counter of the current stimuli sti_count = 0 current_conversation = None nb_stimulis = len(expected_frames_pattern) for frame in frames: # If the frame matches a stimuli if expected_frames_pattern[sti_count].match(frame[protocol]): # If it's the first stimuli if sti_count == 0: # If there is already a conversation pending, save it if current_conversation: self._conversations.append(current_conversation) # Get the nodes as a list of nodes # TODO already done at begining. why isnde the iteeration? # nodes = testcase.get_nodes_identification_templates() # And create the new one current_conversation = Conversation(nodes) # If intermediate stimulis, just increment the counter sti_count = (sti_count + 1) % nb_stimulis # If there is a current_conversation, put the frame into it if current_conversation: current_conversation.append(frame) # If no conversation pending else: ignored.append(frame) # At the end, if there is a current conversation pending, close it if current_conversation: # If not all stimulis were consumed if sti_count != 0: raise FilterError( 'Not all stimulis were consumed, %d left and next one should have been %s' % (nb_stimulis - sti_count, stimulis[sti_count])) # Close the current conversation by adding it to list conversations.append(current_conversation) return conversations, ignored