Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
212 changes: 138 additions & 74 deletions scripts/imgtool/dumpinfo.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,14 @@
"""
Parse and print header, TLV area and trailer information of a signed image.
"""
import json
import os.path
import struct
import sys

import click
import yaml
from intelhex import IntelHex

from imgtool import image

Expand Down Expand Up @@ -74,62 +77,66 @@
return magic


def print_in_frame(header_text, content):
def _human_format_frame(header_text, content):
sepc = " "
header = "#### " + header_text + sepc
post_header = "#" * (_LINE_LENGTH - len(header))
print(header + post_header)

print("|", sepc * (_LINE_LENGTH - 2), "|", sep="")
lines = []
lines.append(header + post_header)
lines.append("|" + sepc * (_LINE_LENGTH - 2) + "|")
offset = (_LINE_LENGTH - len(content)) // 2
pre = "|" + (sepc * (offset - 1))
post = sepc * (_LINE_LENGTH - len(pre) - len(content) - 1) + "|"
print(pre, content, post, sep="")
print("|", sepc * (_LINE_LENGTH - 2), "|", sep="")
print("#" * _LINE_LENGTH)
lines.append(pre + content + post)
lines.append("|" + sepc * (_LINE_LENGTH - 2) + "|")
lines.append("#" * _LINE_LENGTH)
return "\n".join(lines)


def print_in_row(row_text):
def _human_format_row(row_text):
row_text = "#### " + row_text + " "
fill = "#" * (_LINE_LENGTH - len(row_text))
print(row_text + fill)
return row_text + fill


def print_tlv_records(tlv_list):
def _human_format_tlv_records(tlv_list):
indent = _LINE_LENGTH // 8
lines = []
for tlv in tlv_list:
print(" " * indent, "-" * 45)
tlv_type, tlv_length, tlv_data = tlv.keys()
lines.append(" " * indent + " " + "-" * 45)

if tlv[tlv_type] in TLV_TYPES:
print(" " * indent, f"{tlv_type}: {TLV_TYPES[tlv[tlv_type]]} ({hex(tlv[tlv_type])})")
else:
print(" " * indent, "{}: {} ({})".format(
tlv_type, "UNKNOWN", hex(tlv[tlv_type])))
print(" " * indent, f"{tlv_length}: ", hex(tlv[tlv_length]))
print(" " * indent, f"{tlv_data}: ", end="")
type_name = tlv["type_name"]
type_hex = hex(tlv["type"])
lines.append(" " * indent + f" type: {type_name} ({type_hex})")
lines.append(" " * indent + f" len: {hex(tlv['len'])}")

for j, data in enumerate(tlv[tlv_data]):
print(f"{data:#04x}", end=" ")
if ((j + 1) % 8 == 0) and ((j + 1) != len(tlv[tlv_data])):
print("\n", end=" " * (indent + 7))
print()
data_line = " " * indent + " data: "
for j, data in enumerate(tlv["data"]):
data_line += f"{data:#04x} "
if ((j + 1) % 8 == 0) and ((j + 1) != len(tlv["data"])):
lines.append(data_line)
data_line = " " * (indent + 7)
lines.append(data_line)
return "\n".join(lines)


def dump_imginfo(imgfile, outfile=None, silent=False):
"""Parse a signed image binary and print/save the available information."""
def _read_imginfo(imgfile):

Check failure on line 123 in scripts/imgtool/dumpinfo.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this function to reduce its Cognitive Complexity from 25 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=nrfconnect_sdk-mcuboot&issues=AZ2wco879teth8rM0gnX&open=AZ2wco879teth8rM0gnX&pullRequest=654
"""Parse a signed image binary and return the image data structure."""
trailer_magic = None
# set to INVALID by default
swap_size = 0x99
swap_info = 0x99
copy_done = 0x99
image_ok = 0x99
trailer = {}
key_field_len = None

ext = os.path.splitext(imgfile)[1][1:].lower()
try:
with open(imgfile, "rb") as f:
b = f.read()
if ext == image.INTEL_HEX_EXT:
b = IntelHex(imgfile).tobinstr()
else:
with open(imgfile, "rb") as f:
b = f.read()
except FileNotFoundError:
raise click.UsageError(f"Image file not found ({imgfile})")

Expand Down Expand Up @@ -169,7 +176,10 @@
tlv_off += image.TLV_INFO_SIZE
tlv_data = b[tlv_off:(tlv_off + tlv_len)]
tlv_area["tlvs_prot"].append(
{"type": tlv_type, "len": tlv_len, "data": tlv_data})
{"type": tlv_type,
"type_name": TLV_TYPES.get(tlv_type, "UNKNOWN"),
"len": tlv_len,
"data": tlv_data})
tlv_off += tlv_len

_tlv_head = struct.unpack('HH', b[tlv_off:(tlv_off + image.TLV_INFO_SIZE)])
Expand All @@ -187,7 +197,8 @@
tlv_off += image.TLV_INFO_SIZE
tlv_data = b[tlv_off:(tlv_off + tlv_len)]
tlv_area["tlvs"].append(
{"type": tlv_type, "len": tlv_len, "data": tlv_data})
{"type": tlv_type, "type_name": TLV_TYPES.get(tlv_type, "UNKNOWN"),
"len": tlv_len, "data": tlv_data})
tlv_off += tlv_len

_img_pad_size = len(b) - tlv_end
Expand Down Expand Up @@ -236,26 +247,28 @@
# Estimated value of key_field_len is correct if
# BOOT_SWAP_SAVE_ENCTLV is unset
key_field_len = image.align_up(16, max_align) * 2
trailer["key_field_len"] = key_field_len

imginfo = {
"filename": os.path.basename(imgfile),
"header": header,
"tlv_area": tlv_area,
"trailer": trailer}

# Generating output yaml file
if outfile is not None:
imgdata = {"header": header,
"tlv_area": tlv_area,
"trailer": trailer}
with open(outfile, "w") as outf:
# sort_keys - from pyyaml 5.1
yaml.dump(imgdata, outf, sort_keys=False)
return imginfo

###############################################################################

if silent:
return
def _write_format_human(imginfo, outfile):

Check failure on line 261 in scripts/imgtool/dumpinfo.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this function to reduce its Cognitive Complexity from 34 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=nrfconnect_sdk-mcuboot&issues=AZ2wco879teth8rM0gnY&open=AZ2wco879teth8rM0gnY&pullRequest=654
filename = imginfo["filename"]
header = imginfo["header"]
tlv_area = imginfo["tlv_area"]
trailer = imginfo["trailer"]

print("Printing content of signed image:", os.path.basename(imgfile), "\n")
print("Printing content of signed image:", filename, "\n", file=outfile)

# Image header
section_name = "Image header (offset: 0x0)"
print_in_row(section_name)
print(_human_format_row(section_name), file=outfile)
for key, value in header.items():
if key == "flags":
if not value:
Expand All @@ -271,55 +284,106 @@

if not isinstance(value, str):
value = hex(value)
print(key, ":", " " * (19 - len(key)), value, sep="")
print("#" * _LINE_LENGTH)
print(key, ":", " " * (19 - len(key)), value, sep="", file=outfile)
print("#" * _LINE_LENGTH, file=outfile)

# Image payload
_sectionoff = header["hdr_size"]
frame_header_text = f"Payload (offset: {hex(_sectionoff)})"
frame_content = "FW image (size: {} Bytes)".format(hex(header["img_size"]))
print_in_frame(frame_header_text, frame_content)
print(_human_format_frame(frame_header_text, frame_content), file=outfile)

# TLV area
_sectionoff += header["img_size"]
protected_tlv_size = header["protected_tlv_size"]
if protected_tlv_size != 0:
# Protected TLV area
section_name = f"Protected TLV area (offset: {hex(_sectionoff)})"
print_in_row(section_name)
print("magic: ", hex(tlv_area["tlv_hdr_prot"]["magic"]))
print("area size:", hex(tlv_area["tlv_hdr_prot"]["tlv_tot"]))
print_tlv_records(tlv_area["tlvs_prot"])
print("#" * _LINE_LENGTH)
print(_human_format_row(section_name), file=outfile)
print("magic: ", hex(tlv_area["tlv_hdr_prot"]["magic"]), file=outfile)
print("area size:", hex(tlv_area["tlv_hdr_prot"]["tlv_tot"]), file=outfile)
print(_human_format_tlv_records(tlv_area["tlvs_prot"]), file=outfile)
print("#" * _LINE_LENGTH, file=outfile)

_sectionoff += protected_tlv_size
section_name = f"TLV area (offset: {hex(_sectionoff)})"
print_in_row(section_name)
print("magic: ", hex(tlv_area["tlv_hdr"]["magic"]))
print("area size:", hex(tlv_area["tlv_hdr"]["tlv_tot"]))
print_tlv_records(tlv_area["tlvs"])
print("#" * _LINE_LENGTH)

if _img_pad_size:
print(_human_format_row(section_name), file=outfile)
print("magic: ", hex(tlv_area["tlv_hdr"]["magic"]), file=outfile)
print("area size:", hex(tlv_area["tlv_hdr"]["tlv_tot"]), file=outfile)
print(_human_format_tlv_records(tlv_area["tlvs"]), file=outfile)
print("#" * _LINE_LENGTH, file=outfile)

# Check if trailer has data (for image padding and trailer info)
if trailer.get("magic"):
trailer_magic = trailer["magic"]
_sectionoff += tlv_area["tlv_hdr"]["tlv_tot"]
_erased_val = b[_sectionoff]
frame_header_text = f"Image padding (offset: {hex(_sectionoff)})"
frame_content = f"padding ({hex(_erased_val)})"
print_in_frame(frame_header_text, frame_content)
# Note: We don't have access to original binary data here, so skip padding details

# Image trailer
section_name = "Image trailer (offset: unknown)"
print_in_row(section_name)
print(_human_format_row(section_name), file=outfile)
notice = "(Note: some fields may not be used, depending on the update strategy)\n"
notice = '\n'.join(notice[i:i + _LINE_LENGTH] for i in range(0, len(notice), _LINE_LENGTH))
print(notice)
print("swap status: (len: unknown)")
print("enc. keys: ", parse_enc(key_field_len))
print("swap size: ", parse_size(hex(swap_size)))
print("swap_info: ", parse_status(hex(swap_info)))
print("copy_done: ", parse_status(hex(copy_done)))
print("image_ok: ", parse_status(hex(image_ok)))
print("boot magic: ", parse_boot_magic(trailer_magic))
print()
print(notice, file=outfile)
print("swap status: (len: unknown)", file=outfile)
print("enc. keys: ", parse_enc(trailer.get("key_field_len")), file=outfile)

# Only print trailer fields if they exist
if "swap_size" in trailer:
print("swap size: ", parse_size(hex(trailer["swap_size"])), file=outfile)
if "swap_info" in trailer:
print("swap_info: ", parse_status(hex(trailer["swap_info"])), file=outfile)
if "copy_done" in trailer:
print("copy_done: ", parse_status(hex(trailer["copy_done"])), file=outfile)
if "image_ok" in trailer:
print("image_ok: ", parse_status(hex(trailer["image_ok"])), file=outfile)
print("boot magic: ", parse_boot_magic(trailer_magic), file=outfile)
print(file=outfile)

footer = "End of Image "
print_in_row(footer)
print(_human_format_row(footer), file=outfile)

def _json_default_serializer(obj):
"""Convert non-JSON-serializable objects to JSON-serializable format."""
if isinstance(obj, (bytes, bytearray)):
return obj.hex()
raise TypeError(f"Object of type {type(obj).__name__} is not JSON serializable")


def _write_format(imginfo, output_format, out):
"""Write image info in the specified format to the output stream."""
if output_format == 'human':
_write_format_human(imginfo, out)

elif output_format == 'yaml':
yaml.dump(imginfo, out, sort_keys=False)

elif output_format == 'json':
json.dump(imginfo, out, indent=2, default=_json_default_serializer)
if out == sys.stdout:
print() # Add newline after JSON output to stdout
else:
raise ValueError(f"Invalid output format: {output_format}")


def dump_imginfo(imgfile, outfile=None, output_format=None, silent=False):
"""Parse a signed image binary and print/save the available information."""

# Note: silent parameter is kept for backward compatibility but is ignored.
# The function's purpose is to output data, so silent doesn't make sense here.

# Determine output format based on backward compatibility rules
if output_format is None:
if outfile is None:
output_format = 'human' # no --outfile defaults to human-friendly
else:
output_format = 'yaml' # --outfile without --format defaults to yaml

imginfo = _read_imginfo(imgfile)

if outfile:
with open(outfile, "w") as out:
_write_format(imginfo, output_format, out)
else:
_write_format(imginfo, output_format, sys.stdout)

18 changes: 12 additions & 6 deletions scripts/imgtool/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@
keys.RSAPublic : ['256'],
# This two are set to 256 for compatibility, the right would be 512
keys.Ed25519 : ['256', '512'],
keys.Ed25519Public : ['256', '512'],
keys.X25519 : ['256', '512']
}

Expand Down Expand Up @@ -349,8 +350,8 @@
class Image:

def __init__(self, version=None, header_size=IMAGE_HEADER_SIZE,
pad_header=False, pad=False, confirm=False, align=1,
slot_size=0, max_sectors=DEFAULT_MAX_SECTORS,
pad_header=False, pad=False, confirm=False, test=False,
align=1, slot_size=0, max_sectors=DEFAULT_MAX_SECTORS,
overwrite_only=False, endian="little", load_addr=0,
rom_fixed=None, erased_val=None, save_enctlv=False,
security_counter=None, max_align=None,
Expand All @@ -368,6 +369,7 @@
self.pad_header = pad_header
self.pad = pad
self.confirm = confirm
self.test = test
self.align = align
self.slot_size = slot_size
self.max_sectors = max_sectors
Expand Down Expand Up @@ -487,7 +489,7 @@
self.payload = bytes([0] * self.header_size) + \
self.payload

def save(self, path, hex_addr=None):

Check failure on line 492 in scripts/imgtool/image.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this function to reduce its Cognitive Complexity from 20 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=nrfconnect_sdk-mcuboot&issues=AZ2wco6i9teth8rM0gnW&open=AZ2wco6i9teth8rM0gnW&pullRequest=654
"""Save an image from a given file"""
ext = os.path.splitext(path)[1][1:].lower()
if ext == INTEL_HEX_EXT:
Expand All @@ -506,12 +508,14 @@
self.save_enctlv,
self.enctlv_len)
trailer_addr = (self.base_addr + self.slot_size) - trailer_size
if self.confirm and not self.overwrite_only:
if (self.test or self.confirm) and not self.overwrite_only:
magic_align_size = align_up(len(self.boot_magic),
self.max_align)
image_ok_idx = -(magic_align_size + self.max_align)
# If test is set, we leave image_ok at the erased value
flag = bytearray([self.erased_val] * self.max_align)
flag[0] = 0x01 # image_ok = 0x01
if self.confirm:
flag[0] = 0x01 # image_ok = 0x01
h.puts(trailer_addr + trailer_size + image_ok_idx,
bytes(flag))
h.puts(trailer_addr + (trailer_size - len(self.boot_magic)),
Expand Down Expand Up @@ -950,11 +954,13 @@
pbytes = bytearray([self.erased_val] * padding)
pbytes += bytearray([self.erased_val] * (tsize - len(self.boot_magic)))
pbytes += self.boot_magic
if self.confirm and not self.overwrite_only:
if (self.test or self.confirm) and not self.overwrite_only:
magic_size = 16
magic_align_size = align_up(magic_size, self.max_align)
image_ok_idx = -(magic_align_size + self.max_align)
pbytes[image_ok_idx] = 0x01 # image_ok = 0x01
# If test is set, set leave image_ok at the erased value
if self.confirm:
pbytes[image_ok_idx] = 0x01 # image_ok = 0x01
self.payload += pbytes

@staticmethod
Expand Down
Loading
Loading