Python-ELMO is a Python library which offers an encapsulation of the binary tool ELMO, in order to manipulate it easily in Python and SageMath script.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

project_base.py 10.0KB

  1. import os, re
  2. import numpy as np
  3. from os.path import join as pjoin
  4. from .config import (
  5. MODULE_PATH,
  6. ELMO_TOOL_REPOSITORY,
  7. ELMO_INPUT_FILE_NAME,
  8. DEFAULT_HOST,
  9. DEFAULT_PORT,
  10. )
  11. from .utils import write
  12. class SimulationProject:
  13. # TAG: EXCLUDE-FROM-SIMULATION-SEARCH
  14. """ Class to manage a simultion
  15. It contains all the parameters of the simulation and has method to use it
  16. """
  17. _nb_bits_for_nb_challenges = 16
  18. _project_directory = None
  19. ### Define the project
  20. @classmethod
  21. def get_project_directory(cl):
  22. """ """
  23. if cl._project_directory:
  24. return cl._project_directory
  25. else:
  26. raise NotImplementedError()
  27. @classmethod
  28. def set_project_directory(cl, project_directory):
  29. cl._project_directory = project_directory
  30. @classmethod
  31. def get_project_label(cl):
  32. return cl.get_project_directory()
  33. @classmethod
  34. def get_make_directory(cl):
  35. return ''
  36. @classmethod
  37. def get_binary_path(cl):
  38. raise NotImplementedError()
  39. @classmethod
  40. def get_parameters_names(cl):
  41. return set()
  42. def get_challenge_format(self):
  43. raise NotImplementedError()
  44. ### Tools to realize the simulation of the project
  45. def __init__(self, challenges=None):
  46. self.elmo_folder = pjoin(MODULE_PATH, ELMO_TOOL_REPOSITORY)
  47. self.challenges = challenges
  48. self.reset()
  49. def reset(self):
  50. self.is_executed = False
  51. self.has_been_online = False
  52. self._nb_traces = None
  53. self._complete_asmtrace = None
  54. self._complete_results = None
  55. self._complete_printed_data = None
  56. def get_test_challenges(self):
  57. raise NotImplementedError()
  58. def get_random_challenges(self, nb_challenges):
  59. raise NotImplementedError()
  60. def set_challenges(self, challenges):
  61. self.reset()
  62. self.challenges = challenges
  63. def get_input_filename(self):
  64. return pjoin(self.elmo_folder, ELMO_INPUT_FILE_NAME)
  65. def get_printed_data_filename(self):
  66. return pjoin(self.elmo_folder, 'output', 'printdata.txt')
  67. def get_asmtrace_filename(self):
  68. return pjoin(self.elmo_folder, 'output', 'asmoutput', 'asmtrace00001.txt')
  69. def set_input_for_each_challenge(self, input, challenge):
  70. format = self.get_challenge_format()
  71. def aux(sizes, data):
  72. if len(sizes) == 0:
  73. write(input, data)
  74. else:
  75. assert len(data) == sizes[0], 'Incorrect format for challenge. Get {} instead of {}'.format(len(data), sizes[0])
  76. for i in range(sizes[0]):
  77. aux(sizes[1:], data[i])
  78. for num_part in range(len(format)):
  79. aux(format[num_part], challenge[num_part])
  80. def set_input(self, input):
  81. if self.challenges:
  82. assert len(self.challenges) < (1 << self._nb_bits_for_nb_challenges), \
  83. 'The number of challenges must be strictly lower than {}. Currently, there are {} challenges.'.format(
  84. 1 << self._nb_bits_for_nb_challenges,
  85. len(self.challenges),
  86. )
  87. write(input, len(self.challenges), nb_bits=self._nb_bits_for_nb_challenges)
  88. for challenge in self.challenges:
  89. self.set_input_for_each_challenge(input, challenge)
  90. def run(self):
  91. self.reset()
  92. with open(self.get_input_filename(), 'w') as _input:
  93. self.set_input(_input)
  94. from .manage import execute_simulation
  95. res = execute_simulation(self)
  96. self.is_executed = True
  97. self.has_been_online = False
  98. self._nb_traces = res['nb_traces']
  99. return res
  100. def run_online(self, host=DEFAULT_HOST, port=DEFAULT_PORT):
  101. from .server.protocol import SocketTool
  102. import socket
  103. class TempInput:
  104. def __init__(self):
  105. self._buffer = ''
  106. def write(self, data):
  107. self._buffer += data
  108. def get_string(self):
  109. return self._buffer
  110. self.reset()
  111. input = TempInput()
  112. self.set_input(input)
  113. try:
  114. s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  115. s.connect((host, port))
  116. SocketTool.send_data(s, {
  117. 'input': input.get_string(),
  118. })
  119. if not SocketTool.get_ack(s):
  120. raise RuntimeError("NACK received: The request has been refused!")
  121. SocketTool.send_file(s, '{}/{}'.format(self.get_project_directory(), self.get_binary_path()))
  122. if not SocketTool.get_ack(s):
  123. raise RuntimeError("NACK received: The binary file has been refused!")
  124. data = SocketTool.get_data(s)
  125. if data['error']:
  126. raise Exception("The simulation returned an error: {}".format(data['error']))
  127. s.close()
  128. except IOError as err:
  129. raise RuntimeError("The connection refused. Has the ELMO server been switch on?") from err
  130. self.is_executed = True
  131. self.has_been_online = True
  132. self._nb_traces = data['nb_traces']
  133. self._complete_asmtrace = data['asmtrace']
  134. self._complete_results = data['results']
  135. self._complete_printed_data = data['printed_data']
  136. return { key: value
  137. for key, value in data.items()
  138. if key not in ['results', 'asmtrace', 'printed_data']
  139. }
  140. ### Manipulate the results
  141. def get_number_of_challenges(self):
  142. return len(self.challenges)
  143. def get_number_of_traces(self):
  144. assert self.is_executed
  145. return self._nb_traces
  146. def get_results_filenames(self):
  147. assert self.is_executed
  148. assert not self.has_been_online
  149. nb_traces = self.get_number_of_traces()
  150. output_path = os.path.join(self.elmo_folder, 'output')
  151. filenames = []
  152. for i in range(nb_traces):
  153. filename = os.path.join(output_path, 'traces', 'trace%05d.trc' % (i+1))
  154. assert os.path.isfile(filename)
  155. filenames.append(filename)
  156. return filenames
  157. def get_results(self):
  158. """
  159. Warning: The output list is the same object stored in the instance.
  160. If you change this object, it will change in the instance too, and the
  161. next call to 'get_results' will return the changed object.
  162. """
  163. assert self.is_executed
  164. nb_traces = self.get_number_of_traces()
  165. # Load the power traces
  166. if self._complete_results is None:
  167. self._complete_results = []
  168. for filename in self.get_results_filenames():
  169. with open(filename, 'r') as _file:
  170. self._complete_results.append(list(map(float, _file.readlines())))
  171. return self._complete_results
  172. def get_traces(self, indexes=None):
  173. """
  174. """
  175. assert self.is_executed
  176. results = self.get_results()
  177. nb_traces = self.get_number_of_traces()
  178. trace_length = len(results[0])
  179. if indexes is None:
  180. traces = np.zeros((nb_traces, trace_length))
  181. for i in range(nb_traces):
  182. traces[i,:] = results[i]
  183. return traces
  184. else:
  185. traces = np.zeros((nb_traces, len(indexes)))
  186. for i in range(nb_traces):
  187. traces[i,:] = results[i][indexes]
  188. return traces
  189. ### Manipulate the ASM trace
  190. def get_asmtrace(self):
  191. """ Get the ASM trace of the last simulation
  192. The ASM trace is the list of the leaking assembler instructions,
  193. one instruction each point of the leakage power trace
  194. """
  195. assert self.is_executed
  196. # Load the ASM trace
  197. if self._complete_asmtrace is None:
  198. with open(self.get_asmtrace_filename(), 'r') as _file:
  199. self._complete_asmtrace = _file.read()
  200. if type(self._complete_asmtrace):
  201. self._complete_asmtrace = self._complete_asmtrace.split('\n')
  202. return self._complete_asmtrace
  203. def get_indexes_of(self, condition):
  204. """ Get the list of indexes of the instructions
  205. verifying the 'condition' in the ASM trace
  206. :condition: Boolean function with ASM instruction (string) for input
  207. """
  208. assert self.is_executed
  209. return [i for i, instr in enumerate(self.get_asmtrace()) if condition(instr)]
  210. ### Manipulate the Printed Data
  211. def get_printed_data(self, per_trace=True):
  212. """ Get the printed data of the last simulation
  213. A printed data is a data which has been given to the function 'printbyte'
  214. during the simulation
  215. :per_trace: If True (default), split equally the printed data in a table
  216. with a length equal to the number of simulated power traces
  217. """
  218. assert self.is_executed
  219. # Load the printed data
  220. if self._complete_printed_data is None:
  221. with open(self.get_printed_data_filename(), 'r') as _file:
  222. self._complete_printed_data = list(map(lambda x: int(x, 16), _file.readlines()))
  223. if per_trace:
  224. # Return printed data for each trace
  225. data = self._complete_printed_data
  226. nb_traces = self.get_number_of_traces()
  227. nb_data_per_trace = len(data) // nb_traces
  228. return [data[nb_data_per_trace*i:nb_data_per_trace*(i+1)] for i in range(nb_traces)]
  229. else:
  230. # Return printed data
  231. return self._complete_printed_data