2222import getpass
2323import imgtool .keys as keys
2424import sys
25+ import struct
26+ import os
27+ import lzma
28+ import hashlib
2529import base64
2630from imgtool import image , imgtool_version
2731from imgtool .version import decode_version
2832from imgtool .dumpinfo import dump_imginfo
2933from .keys import (
3034 RSAUsageError , ECDSAUsageError , Ed25519UsageError , X25519UsageError )
3135
36+ comp_default_dictsize = 131072
37+ comp_default_pb = 2
38+ comp_default_lc = 3
39+ comp_default_lp = 1
40+ comp_default_preset = 9
41+
42+
3243MIN_PYTHON_VERSION = (3 , 6 )
3344if sys .version_info < MIN_PYTHON_VERSION :
3445 sys .exit ("Python %s.%s or newer is required by imgtool."
@@ -300,6 +311,14 @@ def get_dependencies(ctx, param, value):
300311 dependencies [image .DEP_VERSIONS_KEY ] = versions
301312 return dependencies
302313
314+ def create_lzma2_header (dictsize , pb , lc , lp ):
315+ header = bytearray ()
316+ for i in range (0 , 40 ):
317+ if dictsize <= ((2 | ((i ) & 1 )) << int ((i ) / 2 + 11 )):
318+ header .append (i )
319+ break
320+ header .append ( ( pb * 5 + lp ) * 9 + lc )
321+ return header
303322
304323class BasedIntParamType (click .ParamType ):
305324 name = 'integer'
@@ -343,6 +362,11 @@ def convert(self, value, param, ctx):
343362 type = click .Choice (['128' , '256' ]),
344363 help = 'When encrypting the image using AES, select a 128 bit or '
345364 '256 bit key len.' )
365+ @click .option ('--compression' , default = 'disabled' ,
366+ type = click .Choice (['disabled' , 'lzma2' ]),
367+ help = 'Enable image compression using specified type. '
368+ 'Will fall back without image compression automatically '
369+ 'if the compression increases the image size.' )
346370@click .option ('-c' , '--clear' , required = False , is_flag = True , default = False ,
347371 help = 'Output a non-encrypted image with encryption capabilities,'
348372 'so it can be installed in the primary slot, and encrypted '
@@ -414,10 +438,11 @@ def convert(self, value, param, ctx):
414438 .hex extension, otherwise binary format is used''' )
415439def sign (key , public_key_format , align , version , pad_sig , header_size ,
416440 pad_header , slot_size , pad , confirm , max_sectors , overwrite_only ,
417- endian , encrypt_keylen , encrypt , infile , outfile , dependencies ,
418- load_addr , hex_addr , erased_val , save_enctlv , security_counter ,
419- boot_record , custom_tlv , rom_fixed , max_align , clear , fix_sig ,
420- fix_sig_pubkey , sig_out , user_sha , vector_to_sign , non_bootable ):
441+ endian , encrypt_keylen , encrypt , compression , infile , outfile ,
442+ dependencies , load_addr , hex_addr , erased_val , save_enctlv ,
443+ security_counter , boot_record , custom_tlv , rom_fixed , max_align ,
444+ clear , fix_sig , fix_sig_pubkey , sig_out , user_sha , vector_to_sign ,
445+ non_bootable ):
421446
422447 if confirm :
423448 # Confirmed but non-padded images don't make much sense, because
@@ -431,6 +456,7 @@ def sign(key, public_key_format, align, version, pad_sig, header_size,
431456 erased_val = erased_val , save_enctlv = save_enctlv ,
432457 security_counter = security_counter , max_align = max_align ,
433458 non_bootable = non_bootable )
459+ compression_tlvs = {}
434460 img .load (infile )
435461 key = load_key (key ) if key else None
436462 enckey = load_key (encrypt ) if encrypt else None
@@ -484,10 +510,49 @@ def sign(key, public_key_format, align, version, pad_sig, header_size,
484510 }
485511
486512 img .create (key , public_key_format , enckey , dependencies , boot_record ,
487- custom_tlvs , int (encrypt_keylen ), clear , baked_signature ,
488- pub_key , vector_to_sign , user_sha )
513+ custom_tlvs , compression_tlvs , int (encrypt_keylen ), clear ,
514+ baked_signature , pub_key , vector_to_sign , user_sha )
515+
516+ if compression == "lzma2" :
517+ compressed_img = image .Image (version = decode_version (version ),
518+ header_size = header_size , pad_header = pad_header ,
519+ pad = pad , confirm = confirm , align = int (align ),
520+ slot_size = slot_size , max_sectors = max_sectors ,
521+ overwrite_only = overwrite_only , endian = endian ,
522+ load_addr = load_addr , rom_fixed = rom_fixed ,
523+ erased_val = erased_val , save_enctlv = save_enctlv ,
524+ security_counter = security_counter , max_align = max_align )
525+ compression_filters = [
526+ {"id" : lzma .FILTER_LZMA2 , "preset" : comp_default_preset ,
527+ "dict_size" : comp_default_dictsize , "lp" : comp_default_lp ,
528+ "lc" : comp_default_lc }
529+ ]
530+ compressed_data = lzma .compress (img .get_infile_data (),filters = compression_filters ,
531+ format = lzma .FORMAT_RAW )
532+ uncompressed_size = len (img .get_infile_data ())
533+ compressed_size = len (compressed_data )
534+ print (f"compressed image size: { compressed_size } bytes" )
535+ print (f"original image size: { uncompressed_size } bytes" )
536+ compression_tlvs ["DECOMP_SIZE" ] = struct .pack (
537+ img .get_struct_endian () + 'L' , img .image_size )
538+ compression_tlvs ["DECOMP_SHA" ] = img .image_hash
539+ compression_tlvs_size = len (compression_tlvs ["DECOMP_SIZE" ])
540+ compression_tlvs_size += len (compression_tlvs ["DECOMP_SHA" ])
541+ if img .get_signature ():
542+ compression_tlvs ["DECOMP_SIGNATURE" ] = img .get_signature ()
543+ compression_tlvs_size += len (compression_tlvs ["DECOMP_SIGNATURE" ])
544+ if (compressed_size + compression_tlvs_size ) < uncompressed_size :
545+ compression_header = create_lzma2_header (
546+ dictsize = comp_default_dictsize , pb = comp_default_pb ,
547+ lc = comp_default_lc , lp = comp_default_lp )
548+ compressed_img .load_compressed (compressed_data , compression_header )
549+ compressed_img .base_addr = img .base_addr
550+ compressed_img .create (key , public_key_format , enckey ,
551+ dependencies , boot_record , custom_tlvs , compression_tlvs ,
552+ compression , int (encrypt_keylen ), clear , baked_signature ,
553+ pub_key , vector_to_sign )
554+ img = compressed_img
489555 img .save (outfile , hex_addr )
490-
491556 if sig_out is not None :
492557 new_signature = img .get_signature ()
493558 save_signature (sig_out , new_signature )
0 commit comments