@@ -93,6 +93,11 @@ def is_zeroizable(self) -> bool:
9393 """Check if the partition supports zeroization."""
9494 return getattr (self , 'zeroizable' , False )
9595
96+ @property
97+ def is_absorb (self ) -> bool :
98+ """Check if the partition can use unassigned storage."""
99+ return getattr (self , 'absorb' , False )
100+
96101 @property
97102 def is_empty (self ) -> bool :
98103 """Report if the partition is empty."""
@@ -279,6 +284,43 @@ def emit(fmt, *args):
279284 if offset != rpos :
280285 self ._log .warning ('%s: offset %d, size %d' , self .name , offset , rpos )
281286
287+ def dispatch_absorb (self ) -> None :
288+ """Request the partition to resize its fields."""
289+ if not self .is_absorb :
290+ raise RuntimeError ('Partition cannot absorb free space' )
291+ absorb_fields = {n : v for n , v in self .items .items ()
292+ if v .get ('absorb' , False )}
293+ avail_size = self .size
294+ if self .has_digest :
295+ avail_size -= self .DIGEST_SIZE
296+ if self .is_zeroizable :
297+ avail_size -= self .ZER_SIZE
298+ avail_blocks = avail_size // OtpMap .BLOCK_SIZE
299+ if not absorb_fields :
300+ # a version of OTP where absorb is defined as a partition property
301+ # but not defined for fields. In such a case, it is expected that
302+ # the partition contains a single field.
303+ itemcnt = len (self .items )
304+ if itemcnt != 1 :
305+ raise RuntimeError (f'No known absorption method with { itemcnt } '
306+ f'items in partition { self .name } ' )
307+ # nothing to do here
308+ return
309+ absorb_count = len (absorb_fields )
310+ blk_per_field = avail_blocks // absorb_count
311+ extra_blocks = avail_blocks % absorb_count
312+ self ._log .info ("%s: %d bytes (%d blocks) to absorb into %d field%s" ,
313+ self .name , avail_size , avail_blocks , absorb_count ,
314+ 's' if absorb_count > 1 else '' )
315+ for itname , itfield in absorb_fields .items ():
316+ fsize = itfield ['size' ]
317+ itfield ['size' ] += OtpMap .BLOCK_SIZE * blk_per_field
318+ if extra_blocks :
319+ itfield ['size' ] += OtpMap .BLOCK_SIZE
320+ extra_blocks -= 1
321+ self ._log .info ('%s.%s size augmented from %u to %u bytes' ,
322+ self .name , itname , fsize , itfield ['size' ])
323+
282324 def empty (self ) -> None :
283325 """Empty the partition, including its digest if any."""
284326 self ._data = bytes (len (self ._data ))
0 commit comments