mfg_gen.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580
  1. #!/usr/bin/env python
  2. #
  3. # Copyright 2018 Espressif Systems (Shanghai) PTE LTD
  4. #
  5. # Licensed under the Apache License, Version 2.0 (the "License");
  6. # you may not use this file except in compliance with the License.
  7. # You may obtain a copy of the License at
  8. #
  9. # http://www.apache.org/licenses/LICENSE-2.0
  10. #
  11. # Unless required by applicable law or agreed to in writing, software
  12. # distributed under the License is distributed on an "AS IS" BASIS,
  13. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. # See the License for the specific language governing permissions and
  15. # limitations under the License.
  16. #
  17. from __future__ import print_function
  18. from future.moves.itertools import zip_longest
  19. from io import open
  20. import sys
  21. import os
  22. import argparse
  23. import distutils.dir_util
  24. try:
  25. sys.path.insert(0, os.getenv('IDF_PATH') + "/components/nvs_flash/nvs_partition_generator/")
  26. import nvs_partition_gen
  27. except Exception as e:
  28. print(e)
  29. sys.exit("Please check IDF_PATH")
  30. def verify_values_exist(input_values_file, values_file_data, key_count_in_values_file, line_no=1):
  31. """ Verify all keys have corresponding values in values file
  32. """
  33. if len(values_file_data) != key_count_in_values_file:
  34. raise SystemExit("\nError: Number of values is not equal to number of keys in file: %s at line No:%s\n"
  35. % (str(input_values_file), str(line_no)))
  36. def verify_keys_exist(values_file_keys, config_file_data):
  37. """ Verify all keys from config file are present in values file
  38. """
  39. keys_missing = []
  40. for line_no, config_data in enumerate(config_file_data,1):
  41. if not isinstance(config_data, str):
  42. config_data = config_data.encode('utf-8')
  43. config_data_line = config_data.strip().split(',')
  44. if 'namespace' not in config_data_line:
  45. if values_file_keys:
  46. if config_data_line[0] == values_file_keys[0]:
  47. del values_file_keys[0]
  48. else:
  49. keys_missing.append([config_data_line[0], line_no])
  50. else:
  51. keys_missing.append([config_data_line[0], line_no])
  52. if keys_missing:
  53. for key, line_no in keys_missing:
  54. print("Key:`", str(key), "` at line no:", str(line_no),
  55. " in config file is not found in values file.")
  56. raise SystemExit(1)
  57. def verify_datatype_encoding(input_config_file, config_file_data):
  58. """ Verify datatype and encodings from config file is valid
  59. """
  60. valid_encodings = ["string", "binary", "hex2bin","u8", "i8", "u16", "u32", "i32","base64"]
  61. valid_datatypes = ["file","data","namespace"]
  62. line_no = 0
  63. for data in config_file_data:
  64. line_no += 1
  65. if not isinstance(data, str):
  66. data = data.encode('utf-8')
  67. line = data.strip().split(',')
  68. if line[1] not in valid_datatypes:
  69. raise SystemExit("Error: config file: %s has invalid datatype at line no:%s\n"
  70. % (str(input_config_file), str(line_no)))
  71. if 'namespace' not in line:
  72. if line[2] not in valid_encodings:
  73. raise SystemExit("Error: config file: %s has invalid encoding at line no:%s\n"
  74. % (str(input_config_file), str(line_no)))
  75. def verify_file_data_count(cfg_file_data, keys_repeat):
  76. """ Verify count of data on each line in config file is equal to 3
  77. (as format must be: <key,type and encoding>)
  78. """
  79. line_no = 0
  80. for data in cfg_file_data:
  81. line_no += 1
  82. if not isinstance(data, str):
  83. data = data.encode('utf-8')
  84. line = data.strip().split(',')
  85. if len(line) != 3 and line[0] not in keys_repeat:
  86. raise SystemExit("Error: data missing in config file at line no:%s <format needed:key,type,encoding>\n"
  87. % str(line_no))
  88. def verify_data_in_file(input_config_file, input_values_file, config_file_keys, keys_in_values_file, keys_repeat):
  89. """ Verify count of data on each line in config file is equal to 3 \
  90. (as format must be: <key,type and encoding>)
  91. Verify datatype and encodings from config file is valid
  92. Verify all keys from config file are present in values file and \
  93. Verify each key has corresponding value in values file
  94. """
  95. try:
  96. values_file_keys = []
  97. values_file_line = None
  98. # Get keys from values file present in config files
  99. values_file_keys = get_keys(keys_in_values_file, config_file_keys)
  100. with open(input_config_file, 'r', newline='\n') as cfg_file:
  101. cfg_file_data = cfg_file.readlines()
  102. verify_file_data_count(cfg_file_data, keys_repeat)
  103. verify_datatype_encoding(input_config_file, cfg_file_data)
  104. verify_keys_exist(values_file_keys, cfg_file_data)
  105. with open(input_values_file, 'r', newline='\n') as values_file:
  106. key_count_in_values_file = len(keys_in_values_file)
  107. lineno = 0
  108. # Read first keys(header) line
  109. values_file_data = values_file.readline()
  110. lineno += 1
  111. while values_file_data:
  112. # Read values line
  113. values_file_line = values_file.readline()
  114. if not isinstance(values_file_line, str):
  115. values_file_line = values_file_line.encode('utf-8')
  116. values_file_data = values_file_line.strip().split(',')
  117. lineno += 1
  118. if len(values_file_data) == 1 and '' in values_file_data:
  119. break
  120. verify_values_exist(input_values_file, values_file_data, key_count_in_values_file, line_no=lineno)
  121. except Exception as err:
  122. print(err)
  123. exit(1)
  124. def get_keys(keys_in_values_file, config_file_keys):
  125. """ Get keys from values file present in config file
  126. """
  127. values_file_keys = []
  128. for key in keys_in_values_file:
  129. if key in config_file_keys:
  130. values_file_keys.append(key)
  131. return values_file_keys
  132. def add_config_data_per_namespace(input_config_file):
  133. """ Add config data per namespace to `config_data_to_write` list
  134. """
  135. config_data_to_write = []
  136. config_data_per_namespace = []
  137. with open(input_config_file, 'r', newline='\n') as cfg_file:
  138. config_data = cfg_file.readlines()
  139. # `config_data_per_namespace` is added to `config_data_to_write` list after reading next namespace
  140. for data in config_data:
  141. if not isinstance(data, str):
  142. data = data.encode('utf-8')
  143. cfg_data = data.strip().split(',')
  144. if 'REPEAT' in cfg_data:
  145. cfg_data.remove('REPEAT')
  146. if 'namespace' in cfg_data:
  147. if config_data_per_namespace:
  148. config_data_to_write.append(config_data_per_namespace)
  149. config_data_per_namespace = []
  150. config_data_per_namespace.append(cfg_data)
  151. else:
  152. config_data_per_namespace.append(cfg_data)
  153. else:
  154. config_data_per_namespace.append(cfg_data)
  155. # `config_data_per_namespace` is added to `config_data_to_write` list as EOF is reached
  156. if (not config_data_to_write) or (config_data_to_write and config_data_per_namespace):
  157. config_data_to_write.append(config_data_per_namespace)
  158. return config_data_to_write
  159. def get_fileid_val(file_identifier, keys_in_config_file, keys_in_values_file,
  160. values_data_line, key_value_data, fileid_value):
  161. """ Get file identifier value
  162. """
  163. file_id_found = False
  164. for key in key_value_data:
  165. if file_identifier and not file_id_found and file_identifier in key:
  166. fileid_value = key[1]
  167. file_id_found = True
  168. if not file_id_found:
  169. fileid_value = str(int(fileid_value) + 1)
  170. return fileid_value
  171. def add_data_to_file(config_data_to_write, key_value_pair, output_csv_file):
  172. """ Add data to csv target file
  173. """
  174. header = ['key', 'type', 'encoding', 'value']
  175. data_to_write = []
  176. newline = u"\n"
  177. target_csv_file = open(output_csv_file, 'w', newline=None)
  178. line_to_write = u",".join(header)
  179. target_csv_file.write(line_to_write)
  180. target_csv_file.write(newline)
  181. for namespace_config_data in config_data_to_write:
  182. for data in namespace_config_data:
  183. data_to_write = data[:]
  184. if 'namespace' in data:
  185. data_to_write.append('')
  186. line_to_write = u",".join(data_to_write)
  187. target_csv_file.write(line_to_write)
  188. target_csv_file.write(newline)
  189. else:
  190. key = data[0]
  191. while key not in key_value_pair[0]:
  192. del key_value_pair[0]
  193. if key in key_value_pair[0]:
  194. value = key_value_pair[0][1]
  195. data_to_write.append(value)
  196. del key_value_pair[0]
  197. line_to_write = u",".join(data_to_write)
  198. target_csv_file.write(line_to_write)
  199. target_csv_file.write(newline)
  200. # Set index to start of file
  201. target_csv_file.seek(0)
  202. target_csv_file.close()
  203. def create_dir(filetype, output_dir_path):
  204. """ Create new directory(if doesn't exist) to store file generated
  205. """
  206. output_target_dir = os.path.join(output_dir_path,filetype,'')
  207. if not os.path.isdir(output_target_dir):
  208. distutils.dir_util.mkpath(output_target_dir)
  209. return output_target_dir
  210. def set_repeat_value(total_keys_repeat, keys, csv_file, target_filename):
  211. key_val_pair = []
  212. key_repeated = []
  213. line = None
  214. newline = u"\n"
  215. with open(csv_file, 'r', newline=None) as read_from, open(target_filename,'w', newline=None) as write_to:
  216. headers = read_from.readline()
  217. values = read_from.readline()
  218. write_to.write(headers)
  219. write_to.write(values)
  220. if not isinstance(values, str):
  221. values = values.encode('utf-8')
  222. values = values.strip().split(',')
  223. total_keys_values = list(zip_longest(keys, values))
  224. # read new data, add value if key has repeat tag, write to new file
  225. line = read_from.readline()
  226. if not isinstance(line, str):
  227. line = line.encode('utf-8')
  228. row = line.strip().split(',')
  229. while row:
  230. index = -1
  231. key_val_new = list(zip_longest(keys, row))
  232. key_val_pair = total_keys_values[:]
  233. key_repeated = total_keys_repeat[:]
  234. while key_val_new and key_repeated:
  235. index = index + 1
  236. # if key has repeat tag, get its corresponding value, write to file
  237. if key_val_new[0][0] == key_repeated[0]:
  238. val = key_val_pair[0][1]
  239. row[index] = val
  240. del key_repeated[0]
  241. del key_val_new[0]
  242. del key_val_pair[0]
  243. line_to_write = u",".join(row)
  244. write_to.write(line_to_write)
  245. write_to.write(newline)
  246. # Read next line
  247. line = read_from.readline()
  248. if not isinstance(line, str):
  249. line = line.encode('utf-8')
  250. row = line.strip().split(',')
  251. if len(row) == 1 and '' in row:
  252. break
  253. return target_filename
  254. def create_intermediate_csv(args, keys_in_config_file, keys_in_values_file, keys_repeat, is_encr=False):
  255. file_identifier_value = '0'
  256. csv_str = 'csv'
  257. bin_str = 'bin'
  258. line = None
  259. set_output_keyfile = False
  260. # Add config data per namespace to `config_data_to_write` list
  261. config_data_to_write = add_config_data_per_namespace(args.conf)
  262. try:
  263. with open(args.values, 'r', newline=None) as csv_values_file:
  264. # first line must be keys in file
  265. line = csv_values_file.readline()
  266. if not isinstance(line, str):
  267. line = line.encode('utf-8')
  268. keys = line.strip().split(',')
  269. filename, file_ext = os.path.splitext(args.values)
  270. target_filename = filename + "_created" + file_ext
  271. if keys_repeat:
  272. target_values_file = set_repeat_value(keys_repeat, keys, args.values, target_filename)
  273. else:
  274. target_values_file = args.values
  275. csv_values_file = open(target_values_file, 'r', newline=None)
  276. # Read header line
  277. csv_values_file.readline()
  278. # Create new directory(if doesn't exist) to store csv file generated
  279. output_csv_target_dir = create_dir(csv_str, args.outdir)
  280. # Create new directory(if doesn't exist) to store bin file generated
  281. output_bin_target_dir = create_dir(bin_str, args.outdir)
  282. if args.keygen:
  283. set_output_keyfile = True
  284. line = csv_values_file.readline()
  285. if not isinstance(line, str):
  286. line = line.encode('utf-8')
  287. values_data_line = line.strip().split(',')
  288. while values_data_line:
  289. key_value_data = list(zip_longest(keys_in_values_file, values_data_line))
  290. # Get file identifier value from values file
  291. file_identifier_value = get_fileid_val(args.fileid, keys_in_config_file,
  292. keys_in_values_file, values_data_line, key_value_data,
  293. file_identifier_value)
  294. key_value_pair = key_value_data[:]
  295. # Verify if output csv file does not exist
  296. csv_filename = args.prefix + "-" + file_identifier_value + "." + csv_str
  297. output_csv_file = output_csv_target_dir + csv_filename
  298. if os.path.isfile(output_csv_file):
  299. raise SystemExit("Target csv file: %s already exists.`" % output_csv_file)
  300. # Add values corresponding to each key to csv intermediate file
  301. add_data_to_file(config_data_to_write, key_value_pair, output_csv_file)
  302. print("\nCreated CSV file: ===>", output_csv_file)
  303. # Verify if output bin file does not exist
  304. bin_filename = args.prefix + "-" + file_identifier_value + "." + bin_str
  305. output_bin_file = output_bin_target_dir + bin_filename
  306. if os.path.isfile(output_bin_file):
  307. raise SystemExit("Target binary file: %s already exists.`" % output_bin_file)
  308. args.input = output_csv_file
  309. args.output = os.path.join(bin_str, bin_filename)
  310. if set_output_keyfile:
  311. args.keyfile = "keys-" + args.prefix + "-" + file_identifier_value
  312. if is_encr:
  313. nvs_partition_gen.encrypt(args)
  314. else:
  315. nvs_partition_gen.generate(args)
  316. # Read next line
  317. line = csv_values_file.readline()
  318. if not isinstance(line, str):
  319. line = line.encode('utf-8')
  320. values_data_line = line.strip().split(',')
  321. if len(values_data_line) == 1 and '' in values_data_line:
  322. break
  323. print("\nFiles generated in %s ..." % args.outdir)
  324. except Exception as e:
  325. print(e)
  326. exit(1)
  327. finally:
  328. csv_values_file.close()
  329. def verify_empty_lines_exist(file_name, input_file_data):
  330. for data in input_file_data:
  331. if not isinstance(data, str):
  332. data = data.encode('utf-8')
  333. cfg_data = data.strip().split(',')
  334. if len(cfg_data) == 1 and '' in cfg_data:
  335. raise SystemExit("Error: file: %s cannot have empty lines. " % file_name)
  336. def verify_file_format(args):
  337. keys_in_config_file = []
  338. keys_in_values_file = []
  339. keys_repeat = []
  340. file_data_keys = None
  341. # Verify config file is not empty
  342. if os.stat(args.conf).st_size == 0:
  343. raise SystemExit("Error: config file: %s is empty." % args.conf)
  344. # Verify values file is not empty
  345. if os.stat(args.values).st_size == 0:
  346. raise SystemExit("Error: values file: %s is empty." % args.values)
  347. # Verify config file does not have empty lines
  348. with open(args.conf, 'r', newline='\n') as csv_config_file:
  349. try:
  350. file_data = csv_config_file.readlines()
  351. verify_empty_lines_exist(args.conf, file_data)
  352. csv_config_file.seek(0)
  353. # Extract keys from config file
  354. for data in file_data:
  355. if not isinstance(data, str):
  356. data = data.encode('utf-8')
  357. line_data = data.strip().split(',')
  358. if 'namespace' not in line_data:
  359. keys_in_config_file.append(line_data[0])
  360. if 'REPEAT' in line_data:
  361. keys_repeat.append(line_data[0])
  362. except Exception as e:
  363. print(e)
  364. # Verify values file does not have empty lines
  365. with open(args.values, 'r', newline='\n') as csv_values_file:
  366. try:
  367. # Extract keys from values file (first line of file)
  368. file_data = [csv_values_file.readline()]
  369. file_data_keys = file_data[0]
  370. if not isinstance(file_data_keys, str):
  371. file_data_keys = file_data_keys.encode('utf-8')
  372. keys_in_values_file = file_data_keys.strip().split(',')
  373. while file_data:
  374. verify_empty_lines_exist(args.values, file_data)
  375. file_data = [csv_values_file.readline()]
  376. if '' in file_data:
  377. break
  378. except Exception as e:
  379. print(e)
  380. # Verify file identifier exists in values file
  381. if args.fileid:
  382. if args.fileid not in keys_in_values_file:
  383. raise SystemExit('Error: target_file_identifier: %s does not exist in values file.\n' % args.fileid)
  384. else:
  385. args.fileid = 1
  386. return keys_in_config_file, keys_in_values_file, keys_repeat
  387. def generate(args):
  388. keys_in_config_file = []
  389. keys_in_values_file = []
  390. keys_repeat = []
  391. encryption_enabled = False
  392. args.outdir = os.path.join(args.outdir, '')
  393. # Verify input config and values file format
  394. keys_in_config_file, keys_in_values_file, keys_repeat = verify_file_format(args)
  395. # Verify data in the input_config_file and input_values_file
  396. verify_data_in_file(args.conf, args.values, keys_in_config_file,
  397. keys_in_values_file, keys_repeat)
  398. if (args.keygen or args.inputkey):
  399. encryption_enabled = True
  400. print("\nGenerating encrypted NVS binary images...")
  401. # Create intermediate csv file
  402. create_intermediate_csv(args, keys_in_config_file, keys_in_values_file,
  403. keys_repeat, is_encr=encryption_enabled)
  404. def generate_key(args):
  405. nvs_partition_gen.generate_key(args)
  406. def main():
  407. try:
  408. parser = argparse.ArgumentParser(description="\nESP Manufacturing Utility", formatter_class=argparse.RawTextHelpFormatter)
  409. subparser = parser.add_subparsers(title='Commands',
  410. dest='command',
  411. help='\nRun mfg_gen.py {command} -h for additional help\n\n')
  412. parser_gen = subparser.add_parser('generate',
  413. help='Generate NVS partition',
  414. formatter_class=argparse.RawTextHelpFormatter)
  415. parser_gen.set_defaults(func=generate)
  416. parser_gen.add_argument('conf',
  417. default=None,
  418. help='Path to configuration csv file to parse')
  419. parser_gen.add_argument('values',
  420. default=None,
  421. help='Path to values csv file to parse')
  422. parser_gen.add_argument('prefix',
  423. default=None,
  424. help='Unique name for each output filename prefix')
  425. parser_gen.add_argument('size',
  426. default=None,
  427. help='Size of NVS partition in bytes\
  428. \n(must be multiple of 4096)')
  429. parser_gen.add_argument('--fileid',
  430. default=None,
  431. help='''Unique file identifier(any key in values file) \
  432. \nfor each filename suffix (Default: numeric value(1,2,3...)''')
  433. parser_gen.add_argument('--version',
  434. choices=[1, 2],
  435. default=2,
  436. type=int,
  437. help='''Set multipage blob version.\
  438. \nVersion 1 - Multipage blob support disabled.\
  439. \nVersion 2 - Multipage blob support enabled.\
  440. \nDefault: Version 2 ''')
  441. parser_gen.add_argument('--keygen',
  442. action="store_true",
  443. default=False,
  444. help='Generates key for encrypting NVS partition')
  445. parser_gen.add_argument('--keyfile',
  446. default=None,
  447. help=argparse.SUPPRESS)
  448. parser_gen.add_argument('--inputkey',
  449. default=None,
  450. help='File having key for encrypting NVS partition')
  451. parser_gen.add_argument('--outdir',
  452. default=os.getcwd(),
  453. help='Output directory to store files created\
  454. \n(Default: current directory)')
  455. parser_gen.add_argument('--input',
  456. default=None,
  457. help=argparse.SUPPRESS)
  458. parser_gen.add_argument('--output',
  459. default=None,
  460. help=argparse.SUPPRESS)
  461. parser_gen_key = subparser.add_parser('generate-key',
  462. help='Generate keys for encryption',
  463. formatter_class=argparse.RawTextHelpFormatter)
  464. parser_gen_key.set_defaults(func=generate_key)
  465. parser_gen_key.add_argument('--keyfile',
  466. default=None,
  467. help='Path to output encryption keys file')
  468. parser_gen_key.add_argument('--outdir',
  469. default=os.getcwd(),
  470. help='Output directory to store files created.\
  471. \n(Default: current directory)')
  472. args = parser.parse_args()
  473. args.func(args)
  474. except ValueError as err:
  475. print(err)
  476. except Exception as e:
  477. print(e)
  478. if __name__ == "__main__":
  479. main()