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, allow_abbrev=False)
28
29    parser.add_argument("-f", "--file", required=True, help="Input file")
30    parser.add_argument("-o", "--offset", type=lambda x: int(x, 0), default=0,
31                        help="Byte offset in the input file")
32    parser.add_argument("-l", "--length", type=lambda x: int(x, 0), default=-1,
33                        help="""Length in bytes to read from the input file.
34                        Defaults to reading till the end of the input file.""")
35    parser.add_argument("-m", "--format", default="list",
36                        help="Output format: 'list' (default) or 'literal' (string literal)")
37    parser.add_argument("-g", "--gzip", action="store_true",
38                        help="Compress the file using gzip before output")
39    parser.add_argument("-t", "--gzip-mtime", type=int, default=0,
40                        nargs='?', const=None,
41                        help="""mtime seconds in the gzip header.
42                        Defaults to zero to keep builds deterministic. For
43                        current date and time (= "now") use this option
44                        without any value.""")
45    args = parser.parse_args()
46
47
48def get_nice_string(list_or_iterator):
49    # Convert into comma separated list form.
50    s = ", ".join("0x" + str(x) for x in list_or_iterator)
51
52    # Format the list to eight values per line.
53    return "\n".join(s[i:i+47] for i in range(0, len(s), 48))
54
55
56def make_hex(chunk):
57    hexdata = codecs.encode(chunk, 'hex').decode("utf-8")
58    hexlist = map(''.join, zip(*[iter(hexdata)] * 2))
59    print(get_nice_string(hexlist) + ',')
60
61
62def make_string_literal(chunk):
63    hexdata = codecs.encode(chunk, 'hex').decode("utf-8")
64    hexlist = map(''.join, zip(*[iter(hexdata)] * 2))
65    print(''.join("\\x" + str(x) for x in hexlist), end='')
66
67
68def main():
69    parse_args()
70
71    if args.gzip:
72        with io.BytesIO() as content:
73            with open(args.file, 'rb') as fg:
74                fg.seek(args.offset)
75                with gzip.GzipFile(fileobj=content, mode='w',
76                                   mtime=args.gzip_mtime,
77                                   compresslevel=9) as gz_obj:
78                    gz_obj.write(fg.read(args.length))
79
80            content.seek(0)
81            if args.format == "literal":
82                print('"', end='')
83                for chunk in iter(lambda: content.read(1024), b''):
84                    make_string_literal(chunk)
85                print('"', end='')
86            else:
87                for chunk in iter(lambda: content.read(1024), b''):
88                    make_hex(chunk)
89    else:
90        with open(args.file, "rb") as fp:
91            fp.seek(args.offset)
92
93            if args.format == "literal":
94                if args.length < 0:
95                    print('"', end='')
96                    for chunk in iter(lambda: fp.read(1024), b''):
97                        make_string_literal(chunk)
98                    print('"', end='')
99                else:
100                    print('"', end='')
101                    remainder = args.length
102                    for chunk in iter(lambda: fp.read(min(1024, remainder)), b''):
103                        make_string_literal(chunk)
104                        remainder = remainder - len(chunk)
105                    print('"', end='')
106
107            else:
108                if args.length < 0:
109                    for chunk in iter(lambda: fp.read(1024), b''):
110                        make_hex(chunk)
111                else:
112                    remainder = args.length
113                    for chunk in iter(lambda: fp.read(min(1024, remainder)), b''):
114                        make_hex(chunk)
115                        remainder = remainder - len(chunk)
116
117
118if __name__ == "__main__":
119    main()
120