1#!/usr/bin/env python3
2#
3# Copyright (c) 2017 Intel Corporation
4# Copyright (c) 2025 Siemens AG
5#
6# SPDX-License-Identifier: Apache-2.0
7
8
9"""Convert a file to a list of hex characters
10
11The list of hex characters can then be included to a source file. Optionally,
12the output can be compressed.
13
14"""
15
16import argparse
17import codecs
18import gzip
19import io
20
21
22def parse_args():
23    global args
24
25    parser = argparse.ArgumentParser(
26        description=__doc__,
27        formatter_class=argparse.RawDescriptionHelpFormatter,
28        allow_abbrev=False,
29    )
30
31    parser.add_argument("-f", "--file", required=True, help="Input file")
32    parser.add_argument(
33        "-o", "--offset", type=lambda x: int(x, 0), default=0, help="Byte offset in the input file"
34    )
35    parser.add_argument(
36        "-l",
37        "--length",
38        type=lambda x: int(x, 0),
39        default=-1,
40        help="""Length in bytes to read from the input file.
41                Defaults to reading till the end of the input file.""",
42    )
43    parser.add_argument(
44        "-m",
45        "--format",
46        default="list",
47        help="Output format: 'list' (default) or 'literal' (string literal)",
48    )
49    parser.add_argument(
50        "-g", "--gzip", action="store_true", help="Compress the file using gzip before output"
51    )
52    parser.add_argument(
53        "-t",
54        "--gzip-mtime",
55        type=int,
56        default=0,
57        nargs='?',
58        const=None,
59        help="""mtime seconds in the gzip header.
60                Defaults to zero to keep builds deterministic. For
61                current date and time (= "now") use this option
62                without any value.""",
63    )
64    args = parser.parse_args()
65
66
67def get_nice_string(list_or_iterator):
68    # Convert into comma separated list form.
69    s = ", ".join("0x" + str(x) for x in list_or_iterator)
70
71    # Format the list to eight values per line.
72    return "\n".join(s[i : i + 47] for i in range(0, len(s), 48))
73
74
75def make_hex(chunk):
76    hexdata = codecs.encode(chunk, 'hex').decode("utf-8")
77    hexlist = map(''.join, zip(*[iter(hexdata)] * 2, strict=False))
78    print(get_nice_string(hexlist) + ',')
79
80
81def make_string_literal(chunk):
82    hexdata = codecs.encode(chunk, 'hex').decode("utf-8")
83    hexlist = map(''.join, zip(*[iter(hexdata)] * 2, strict=False))
84    print(''.join("\\x" + str(x) for x in hexlist), end='')
85
86
87def chunker(source, remaining=-1):
88    while chunk_raw := source.read(1024):
89        if chunk_raw == b"":
90            break
91        if remaining == -1:
92            yield chunk_raw
93        elif remaining < len(chunk_raw):
94            yield chunk_raw[:remaining]
95        else:
96            yield chunk_raw
97            remaining -= len(chunk_raw)
98
99
100def main():
101    parse_args()
102
103    if args.gzip:
104        with io.BytesIO() as content:
105            with open(args.file, 'rb') as fg:
106                fg.seek(args.offset)
107                with gzip.GzipFile(
108                    fileobj=content, mode='w', mtime=args.gzip_mtime, compresslevel=9
109                ) as gz_obj:
110                    gz_obj.write(fg.read(args.length))
111
112            content.seek(0)
113            if args.format == "literal":
114                print('"', end='')
115                for chunk in chunker(content):
116                    make_string_literal(chunk)
117                print('"', end='')
118            else:
119                for chunk in chunker(content):
120                    make_hex(chunk)
121    else:
122        with open(args.file, "rb") as fp:
123            fp.seek(args.offset)
124
125            if args.format == "literal":
126                print('"', end='')
127                for chunk in chunker(fp, args.length):
128                    make_string_literal(chunk)
129                print('"', end='')
130            else:
131                for chunk in chunker(fp, args.length):
132                    make_hex(chunk)
133
134
135if __name__ == "__main__":
136    main()
137