def __init__(self): self.sigFilenameChanged = util.Signal() self.sigStatusChanged = util.Signal() self.dirty = util.Observable(True) self.settingsDict = {'Project Name':"Untitled", 'Project File Version':"1.00", 'Project Author':"Unknown"} self.datadirectory = "" self.config = ConfigObjProj(callback=self.configObjChanged) self._traceManager = TraceManager().register() self._traceManager.dirty.connect(self.__dirtyCallback) self.setFilename(ProjectFormat.untitledFileName) if __debug__: logging.debug('Created: ' + str(self))
def __init__(self, prog_name="ChipWhisperer", prog_ver=""): self.valid_traces = None self._trace_format = None self.params = Parameter(name="Project Settings", type="group") self.params.addChildren([ { 'name': 'Trace Format', 'type': 'list', 'values': self.valid_traces, 'get': self.get_trace_format, 'set': self.set_trace_format }, ]) #self.findParam("Trace Format").setValue(TraceContainerNative(project=self), addToList=True) self._trace_format = TraceContainerNative(project=self) #self.traceParam = Parameter(name="Trace Settings", type='group', addLoadSave=True).register() #self.params.getChild('Trace Format').stealDynamicParameters(self.traceParam) self.sigFilenameChanged = util.Signal() self.sigStatusChanged = util.Signal() self.dirty = util.Observable(True) self.settingsDict = { 'Project Name': "Untitled", 'Project File Version': "1.00", 'Project Author': "Unknown" } self.datadirectory = "" self.config = ConfigObjProj(callback=self.configObjChanged) self._traceManager = TraceManager().register() self._traceManager.dirty.connect(self.__dirtyCallback) self.setFilename(Project.untitledFileName) self.setProgramName(prog_name) self.setProgramVersion(prog_ver) self._segments = Segments(self) self._traces = Traces(self) self._keys = IndividualIterable(self._traceManager.get_known_key, self._traceManager.num_traces) self._textins = IndividualIterable(self._traceManager.get_textin, self._traceManager.num_traces) self._textouts = IndividualIterable(self._traceManager.get_textout, self._traceManager.num_traces) self._waves = IndividualIterable(self._traceManager.get_trace, self._traceManager.num_traces) if __debug__: logging.debug('Created: ' + str(self))
class Project(Parameterized): """Class describing an open ChipWhisperer project. Basic capture usage:: import chipwhisperer as cw proj = cw.create_project("project") trace = cw.Trace(trace_data, plaintext, ciphertext, key) proj.traces.append(trace) proj.save() Basic analyzer usage:: import chipwhisperer as cw import chipwhisperer.analyzer as cwa proj = cw.open_project("project") attack = cwa.cpa(proj) #run attack Use a trace_manager when analyzing traces, since that allows analyzer to work with multiple trace segments. * :attr:`project.location <.Project.location>` * :attr:`project.waves <.Project.waves>` * :attr:`project.textins <.Project.textins>` * :attr:`project.textouts <.Project.textouts>` * :attr:`project.keys <.Project.keys>` * :meth:`project.get_filename <.Project.get_filename>` * :meth:`project.trace_manager <.Project.trace_manager>` * :meth:`project.save <.Project.save>` * :meth:`project.export <.Project.export>` """ untitledFileName = os.path.normpath( os.path.join(Settings().value("project-home-dir"), "tmp", "default.cwp")) def __init__(self, prog_name="ChipWhisperer", prog_ver=""): self.valid_traces = None self._trace_format = None self.params = Parameter(name="Project Settings", type="group") self.params.addChildren([ { 'name': 'Trace Format', 'type': 'list', 'values': self.valid_traces, 'get': self.get_trace_format, 'set': self.set_trace_format }, ]) #self.findParam("Trace Format").setValue(TraceContainerNative(project=self), addToList=True) self._trace_format = TraceContainerNative(project=self) #self.traceParam = Parameter(name="Trace Settings", type='group', addLoadSave=True).register() #self.params.getChild('Trace Format').stealDynamicParameters(self.traceParam) self.sigFilenameChanged = util.Signal() self.sigStatusChanged = util.Signal() self.dirty = util.Observable(True) self.settingsDict = { 'Project Name': "Untitled", 'Project File Version': "1.00", 'Project Author': "Unknown" } self.datadirectory = "" self.config = ConfigObjProj(callback=self.configObjChanged) self._traceManager = TraceManager().register() self._traceManager.dirty.connect(self.__dirtyCallback) self.setFilename(Project.untitledFileName) self.setProgramName(prog_name) self.setProgramVersion(prog_ver) self._segments = Segments(self) self._traces = Traces(self) self._keys = IndividualIterable(self._traceManager.get_known_key, self._traceManager.num_traces) self._textins = IndividualIterable(self._traceManager.get_textin, self._traceManager.num_traces) self._textouts = IndividualIterable(self._traceManager.get_textout, self._traceManager.num_traces) self._waves = IndividualIterable(self._traceManager.get_trace, self._traceManager.num_traces) if __debug__: logging.debug('Created: ' + str(self)) def append_segment(self, segment): """ Adds a new trace segment to the project Args: segment (TraceContainer): Trace segment to add to project """ self._traceManager.appendSegment(copy.copy(segment)) appendSegment = append_segment def new_segment(self): """ Returns the __class__() of the trace container used to store traces in the project You probably want get_new_trace_segment() Returns: __class()__ of the current trace container """ return self._trace_format.__class__() newSegment = new_segment def get_trace_format(self): """ Gets the TraceContainer used to store traces Returns: The trace container used in the project """ return self._trace_format getTraceFormat = get_trace_format def get_new_trace_segment(self): """Return a new TraceContainer object for the specified format. Once you're done working with this segment, you can readd it to the project with append_segment() Returns: a new TraceContainer object """ tmp = copy.copy(self._trace_format) tmp.clear() starttime = datetime.now() prefix = starttime.strftime('%Y.%m.%d-%H.%M.%S') + "_" tmp.config.setConfigFilename( os.path.join(self.datadirectory, "traces", "config_" + prefix + ".cfg")) tmp.config.setAttr("prefix", prefix) tmp.config.setAttr("date", starttime.strftime('%Y-%m-%d %H:%M:%S')) return tmp getNewTraceSegment = get_new_trace_segment @setupSetParam("Trace Format") def set_trace_format(self, trace_format): self._trace_format = trace_format setTraceFormat = util.camel_case_deprecated(set_trace_format) def __dirtyCallback(self): self.dirty.setValue(self._traceManager.dirty.value() or self.dirty.value()) def configObjChanged(self, key): self.dirty.setValue(True) def isUntitled(self): return self.filename == Project.untitledFileName def trace_manager(self): """ Gets the trace manager for the project Returns: The trace manager for the project """ return self._traceManager traceManager = util.camel_case_deprecated(trace_manager) def setProgramName(self, name): self.settingsDict['Program Name'] = name def setProgramVersion(self, ver): self.settingsDict['Program Version'] = ver def setAuthor(self, author): self.settingsDict['Project Author'] = author def setProjectName(self, name): self.settingsDict['Project Name'] = name def setFileVersion(self, ver): self.settingsDict['Project File Version'] = ver def get_filename(self): """Gets the filename associated with the project. Returns: Filename of the project config file ending with ".cwp". """ return self.filename getFilename = util.camel_case_deprecated(get_filename) def setFilename(self, f): self.filename = f self.config.filename = f self.datadirectory = os.path.splitext(self.filename)[0] + "_data" self.createDataDirectory() self.sigStatusChanged.emit() def createDataDirectory(self): # Check if data-directory exists? if not os.path.isdir(self.datadirectory): os.makedirs(self.datadirectory) # Make trace storage directory too if not os.path.isdir(os.path.join(self.datadirectory, 'traces')): os.mkdir(os.path.join(self.datadirectory, 'traces')) # Make analysis storage directory if not os.path.isdir(os.path.join(self.datadirectory, 'analysis')): os.mkdir(os.path.join(self.datadirectory, 'analysis')) # Make glitchresults storage directory if not os.path.isdir(os.path.join(self.datadirectory, 'glitchresults')): os.mkdir(os.path.join(self.datadirectory, 'glitchresults')) def load(self, f=None): if f is not None: self.setFilename(os.path.abspath(os.path.expanduser(f))) if not os.path.isfile(self.filename): raise IOError("File " + self.filename + " does not exist or is not a file") self.config = ConfigObjProj(infile=self.filename, callback=self.configObjChanged) self._traceManager.loadProject(self.filename) self.dirty.setValue(False) def getDataFilepath(self, filename, subdirectory='analysis'): datadir = os.path.join(self.datadirectory, subdirectory) fname = os.path.join(datadir, filename) relfname = os.path.relpath(fname, os.path.split(self.config.filename)[0]) fname = os.path.normpath(fname) relfname = os.path.normpath(relfname) return {"abs": fname, "rel": relfname} def convertDataFilepathAbs(self, relativepath): return os.path.join(os.path.split(self.filename)[0], relativepath) def checkDataConfig(self, config, requiredSettings): """Check a configuration section for various settings. Don't use.""" requiredSettings = util.convert_to_str(requiredSettings) config = util.convert_to_str(config) return set(requiredSettings.items()).issubset(set(config.items())) def getDataConfig(self, sectionName="Aux Data", subsectionName=None, requiredSettings=None): """ Don't use. Get all configuration sections of data type given in __init__() call, and also matching the given sectionName. e.g. if dataName='Aux Data' and sectionName='Frequency', this would return a list of all sections of the type 'Aux Data NNNN - Frequency'. """ sections = [] # Get all section names for sname in list(self.config.keys()): # Find if starts with 'Aux Data' if sname.startswith(sectionName): # print "Found %s" % sname # Find if module name matches if applicable if subsectionName is None or sname.endswith(subsectionName): # print "Found %s" % sname if requiredSettings is None: sections.append(self.config[sname]) else: self.checkDataConfig(self.config[sname], requiredSettings) return sections def addDataConfig(self, settings=None, sectionName="Aux Data", subsectionName=None): # Check configuration file to find incrementing number. Don't use maxNumber = 0 for sname in list(self.config.keys()): # Find if starts with 'Aux Data' if sname.startswith(sectionName): maxNumber = int(re.findall(r'\d+', sname)[0]) + 1 cfgSectionName = "%s %04d" % (sectionName, maxNumber) if subsectionName: cfgSectionName += " - %s" % subsectionName # Generate the configuration section self.config[cfgSectionName] = {} if settings is not None: for k in list(settings.keys()): self.config[cfgSectionName][k] = settings[k] return self.config[cfgSectionName] def saveAllSettings(self, fname=None, onlyVisibles=False): """ Save registered parameters to a file, so it can be loaded again latter. Don't use.""" if fname is None: fname = os.path.join(self.datadirectory, 'settings.cwset') logging.info('Saving settings to file: ' + fname) Parameter.saveRegistered(fname, onlyVisibles) def saveTraceManager(self): #Waveform list is Universal across ALL types. Don't use. if 'Trace Management' not in self.config: self.config['Trace Management'] = {} self._traceManager.save_project(self.config, self.filename) def save(self): """Saves the project. Writes the project to the disk. Before this is called your data is not saved. Filenames for traces are set in this method. """ if self.filename is None: return self.saveTraceManager() #self.config['Waveform List'] = self.config['Waveform List'] + self.waveList #Program-Specific Options pn = self.settingsDict['Program Name'] self.config[pn] = {} self.config[pn]['General Settings'] = self.settingsDict self.config.write() self.sigStatusChanged.emit() self.dirty.setValue(False) def checkDiff(self): """ Don't use. Check if there is a difference - returns True if so, and False if no changes present. Also updates widget with overview of the differences if requested with updateGUI """ self.saveTraceManager() disk = util.convert_to_str(ConfigObjProj(infile=self.filename)) ram = util.convert_to_str(self.config) def hasDiffs(self): if self.dirty.value(): return True #added, removed, changed = self.checkDiff() if (len(added) + len(removed) + len(changed)) == 0: return False return True def consolidate(self, keepOriginals=True): for indx, t in enumerate(self._traceManager.traceSegments): destinationDir = os.path.normpath( os.path.join(self.datadirectory, "traces")) config = ConfigObj(t.config.configFilename()) prefix = config['Trace Config']['prefix'] tracePath = os.path.normpath( os.path.split(t.config.configFilename())[0]) if destinationDir == tracePath: continue for traceFile in os.listdir(tracePath): if traceFile.startswith(prefix): util.copyFile( os.path.normpath(os.path.join(tracePath, traceFile)), destinationDir, keepOriginals) util.copyFile(t.config.configFilename(), destinationDir, keepOriginals) t.config.setConfigFilename( os.path.normpath( os.path.join(destinationDir, os.path.split(t.config.configFilename())[1]))) self.sigStatusChanged.emit() def __del__(self): if __debug__: logging.debug('Deleted: ' + str(self)) @property def location(self): """The directory in which the project is located. Example:: print(project.location) '/path/to/the/directory/containing/this/project' :Getter: (str) Returns the file path of the projects parent directory. .. versionadded:: 5.1 Added **location** attribute to project. """ return os.path.dirname(os.path.abspath(self.get_filename())) def export(self, file_path, file_type='zip'): """Export a chipwhisperer project. Saves project before exporting. Supported export types: * zip (default) Returns: (str) Path to the exported file. .. versionadded:: 5.1 Add **export** method to active project. """ self.save() _, cwp_file = os.path.split(self.get_filename()) name, ext = os.path.splitext(cwp_file) data_folder = os.path.join(self.location, '_'.join([name, 'data'])) file_paths = list() file_paths.append(os.path.join(self.location, cwp_file)) for root, directories, files in os.walk(data_folder): for filename in files: file_paths.append(os.path.join(root, filename)) if file_type == 'zip': file_path = os.path.abspath(file_path) with zipfile.ZipFile(file_path, 'w') as zip: common_path = os.path.commonpath(file_paths) for file in file_paths: relative_path = os.path.relpath(file, common_path) zip.write(file, arcname=relative_path) export_file_path = os.path.abspath(zip.filename) else: raise ValueError('{} not supported'.format(file_type)) return export_file_path def close(self, save=True): """Closes the project cleanly. Saves by default. Then closes all claimed files. Args: save (bool): Saves the project before closing. """ if save: self.save() for seg in self.segments: seg.unloadAllTraces() def remove(self, i_am_sure=False): """Remove a project from disk. Args: i_am_sure (bool): Are you sure you want to remove the project? """ if not i_am_sure: raise RuntimeWarning( 'Project not removed... i_am_sure not set to True.') self.close(save=False) try: shutil.rmtree(self.datadirectory) except FileNotFoundError: pass _, cwp_file = os.path.split(self.get_filename()) try: os.remove(os.path.join(self.location, cwp_file)) except FileNotFoundError: pass @property def traces(self): """The interface to all traces contained in the project. Instance of :class:`.Traces`. """ return self._traces @property def segments(self): """The interface to all segments contained in the project. Instance of :class:`.Segments`. """ return self._segments @property def keys(self): """Iterable for working with only the known keys. Each item in the iterable is a byte array. Supports iterating, indexing, and slicing:: for key in my_project.keys: # do something """ return self._keys @property def textins(self): """Iterable for working with only the text in. Each item in the iterable is a byte array. Supports iterating, indexing, and slicing:: for textin in my_project.textins: # do something """ return self._textins @property def textouts(self): """Iterable for working with only the text out. Each item in the iterable is a byte array. Supports iterating, indexing, and slicing:: for textout in my_project.textouts: # do something """ return self._textouts @property def waves(self): """Iterable for working with only the trace data. Each item in the iterable is a numpy array. Supports iterating, indexing, and slicing:: for wave in my_project.waves: # do something """ return self._waves
class ProjectFormat(object): untitledFileName = os.path.normpath(os.path.join(Settings().value("project-home-dir"), "tmp/default.cwp")) def __init__(self): self.sigFilenameChanged = util.Signal() self.sigStatusChanged = util.Signal() self.dirty = util.Observable(True) self.settingsDict = {'Project Name':"Untitled", 'Project File Version':"1.00", 'Project Author':"Unknown"} self.datadirectory = "" self.config = ConfigObjProj(callback=self.configObjChanged) self._traceManager = TraceManager().register() self._traceManager.dirty.connect(self.__dirtyCallback) self.setFilename(ProjectFormat.untitledFileName) if __debug__: logging.debug('Created: ' + str(self)) def __dirtyCallback(self): self.dirty.setValue(self._traceManager.dirty.value() or self.dirty.value()) def configObjChanged(self, key): self.dirty.setValue(True) def isUntitled(self): return self.filename == ProjectFormat.untitledFileName def traceManager(self): return self._traceManager def setProgramName(self, name): self.settingsDict['Program Name']=name def setProgramVersion(self, ver): self.settingsDict['Program Version']=ver def setAuthor(self, author): self.settingsDict['Project Author']=author def setProjectName(self, name): self.settingsDict['Project Name']=name def setFileVersion(self, ver): self.settingsDict['Project File Version']=ver def addWave(self, configfile): return def getFilename(self): return self.filename def setFilename(self, f): self.filename = f self.config.filename = f self.datadirectory = os.path.splitext(self.filename)[0] + "_data/" self.createDataDirectory() self.sigStatusChanged.emit() def createDataDirectory(self): # Check if data-directory exists? if not os.path.isdir(self.datadirectory): os.makedirs(self.datadirectory) # Make trace storage directory too if not os.path.isdir(os.path.join(self.datadirectory, 'traces')): os.mkdir(os.path.join(self.datadirectory, 'traces')) # Make analysis storage directory if not os.path.isdir(os.path.join(self.datadirectory, 'analysis')): os.mkdir(os.path.join(self.datadirectory, 'analysis')) # Make glitchresults storage directory if not os.path.isdir(os.path.join(self.datadirectory, 'glitchresults')): os.mkdir(os.path.join(self.datadirectory, 'glitchresults')) def load(self, f = None): if f is not None: self.setFilename(f) self.config = ConfigObjProj(infile=self.filename, callback=self.configObjChanged) self._traceManager.loadProject(self.filename) self.dirty.setValue(False) def getDataFilepath(self, filename, subdirectory='analysis'): datadir = os.path.join(self.datadirectory, subdirectory) fname = os.path.join(datadir, filename) relfname = os.path.relpath(fname, os.path.split(self.config.filename)[0]) fname = os.path.normpath(fname) relfname = os.path.normpath(relfname) return {"abs":fname, "rel":relfname} def convertDataFilepathAbs(self, relativepath): return os.path.join(os.path.split(self.filename)[0], relativepath) def checkDataConfig(self, config, requiredSettings): """Check a configuration section for various settings""" requiredSettings = util.convert_to_str(requiredSettings) config = util.convert_to_str(config) return set(requiredSettings.items()).issubset(set(config.items())) def getDataConfig(self, sectionName="Aux Data", subsectionName=None, requiredSettings=None): """ Get all configuration sections of data type given in __init__() call, and also matching the given sectionName. e.g. if dataName='Aux Data' and sectionName='Frequency', this would return a list of all sections of the type 'Aux Data NNNN - Frequency'. """ sections = [] # Get all section names for sname in self.config.keys(): # Find if starts with 'Aux Data' if sname.startswith(sectionName): # print "Found %s" % sname # Find if module name matches if applicable if subsectionName is None or sname.endswith(subsectionName): # print "Found %s" % sname if requiredSettings is None: sections.append(self.config[sname]) else: self.checkDataConfig(self.config[sname], requiredSettings) return sections def addDataConfig(self, settings=None, sectionName="Aux Data", subsectionName=None): # Check configuration file to find incrementing number maxNumber = 0 for sname in self.config.keys(): # Find if starts with 'Aux Data' if sname.startswith(sectionName): maxNumber = int(re.findall(r'\d+', sname)[0]) + 1 cfgSectionName = "%s %04d" % (sectionName, maxNumber) if subsectionName: cfgSectionName += " - %s" % subsectionName # Generate the configuration section self.config[cfgSectionName] = {} if settings is not None: for k in settings.keys(): self.config[cfgSectionName][k] = settings[k] return self.config[cfgSectionName] def saveAllSettings(self, fname=None, onlyVisibles=False): """ Save registered parameters to a file, so it can be loaded again latter.""" if fname is None: fname = os.path.join(self.datadirectory, 'settings.cwset') logging.info('Saving settings to file: ' + fname) Parameter.saveRegistered(fname, onlyVisibles) def saveTraceManager(self): #Waveform list is Universal across ALL types if 'Trace Management' not in self.config: self.config['Trace Management'] = {} self._traceManager.saveProject(self.config, self.filename) def save(self): if self.filename is None: return self.saveTraceManager() #self.config['Waveform List'] = self.config['Waveform List'] + self.waveList #Program-Specific Options pn = self.settingsDict['Program Name'] self.config[pn] = {} self.config[pn]['General Settings'] = self.settingsDict self.config.write() self.sigStatusChanged.emit() self.dirty.setValue(False) def checkDiff(self): """ Check if there is a difference - returns True if so, and False if no changes present. Also updates widget with overview of the differences if requested with updateGUI """ self.saveTraceManager() disk = util.convert_to_str(ConfigObjProj(infile=self.filename)) ram = util.convert_to_str(self.config) diff = DictDiffer(ram, disk) added = diff.added() removed = diff.removed() changed = diff.changed() #TODO: bug when comparing projects with template sections. It is returning changes when there is not. return added, removed, changed def hasDiffs(self): if self.dirty.value(): return True added, removed, changed = self.checkDiff() if (len(added) + len(removed) + len(changed)) == 0: return False return True def consolidate(self, keepOriginals = True): for indx, t in enumerate(self._traceManager.traceSegments): destinationDir = os.path.normpath(self.datadirectory + "traces/") config = ConfigObj(t.config.configFilename()) prefix = config['Trace Config']['prefix'] tracePath = os.path.normpath(os.path.split(t.config.configFilename())[0]) if destinationDir == tracePath: continue for traceFile in os.listdir(tracePath): if traceFile.startswith(prefix): util.copyFile(os.path.normpath(tracePath + "/" + traceFile), destinationDir, keepOriginals) util.copyFile(t.config.configFilename(), destinationDir, keepOriginals) t.config.setConfigFilename(os.path.normpath(destinationDir + "/" + os.path.split(t.config.configFilename())[1])) self.sigStatusChanged.emit() def __del__(self): if __debug__: logging.debug('Deleted: ' + str(self))